当前位置: 首页 > Linux

bash中的命令类型

时间:2023-04-06 04:15:05 Linux

Bash支持的命令类型包括以下三种:shell函数:根据shell编程的语法构造的可多次调用的代码块。与其他语言不同的是,shell中的函数没有正式的参数列表,但是在调用函数时可以传入任意数量的参数,并且可以通过函数内部的$N获取指定位置的参数。我们可以使用typeset-f命令查看当前shell中定义的所有函数,可以使用下面的命令直接显示函数名。>typeset-f|awk'$2~/\(\)/{print$1}'>typeset-f|sed-En"s/(.*)\(\)/\1/p">typeset-f|grep"()"内置命令:shell在源代码级别提供的命令,而不是存在于文件系统中的可执行文件。例如,用于进入或切换目录的cd命令不是外部文件。执行内置命令时,不需要fork子进程,也不需要使用exec加载外部可执行文件,不会触发磁盘I/O。执行一个内置命令相当于执行当前shell源代码中的一个函数。我们可以通过type来判断一个命令是否是内置命令:>typecdaliascdisashellbuiltinaliasisashellbuiltin>compgen-b#显示所有内置命令外部可执行文件:一般是编译好的二进制文件,bash会要在当前Path和PATH环境变量中的路径中查找可执行文件,bash使用哈希表(内存中的数据存储区)来记住可执行文件的完整路径名,用来避免重复的全局搜索。>man你要什么手册页?>typemanmanishashed(/usr/bin/man)#再次访问时,已经被哈希表缓存了>whichman#查看外部命令的路径/usr/bin/man查询命令时,优先级是shell函数>内置命令>外部可执行文件,例如GNU标准的echo可执行文件在Ubuntu系统中自带/usr/bin/路径,还有一个内置的-在bash中的命令echo中。根据命令查询的先后顺序,在bash中使用echo时,会先执行内置命令。在bash中,还可以使用alias为常用命令添加别名。aliases的优先级高于上面三个常规命令>whichls/usr/bin/ls#是一个外部命令>typelslsisaliasedto`ls--color=auto'#aliasls会自动显示结构color命令实现shell函数命令在bash中定义函数时,关键字function可以省略,函数中可能包含内置命令和外部命令、变量、数组、关键字等,是代码块中的本质,但有一些类似于其他语言中函数的特性。函数的其他语法细节请参考GNUBash手册。thenecho"Usage:pskill"return-127filocalpid#设置作用域为本地,即代码块内部#如果不这样写,默认是全局变量pid=$(ps-ax|grep$1|grep-vgrep|awk'{print$1}')kill-9$pid&#提交作业echo-n"killing$1(process$pid)..."wait#waitforthejobtocompleteecho"slaughtered."}与其他语言相比,bash中的函数没有固定的参数列表,优点是它们的调用形式更像普通命令,例如:函数名<参数1><参数2>...,但缺点是如果在函数内部不判断参数个数和合法性,很容易出现严重的运行时错误发生。>pskillUsage:pskill>xclock&[1]3003>pskillxclock[2]3043killingxclock(process3003)...[1]-Killedxclock[2]+Donekill-9$pidslaughtered.内置命令的实现本节使用5.0版本的GUNbash源码进行讲解,修改源码前,建议确认你的环境是否可以成功配置编译bash:>gitclonehttps://git.savannah.gnu.org/git/bash.git>./configure>make#如果成功会在当前目录生成可执行文件现在我们开始实现一个内置的命令scarlet,它的功能和eval一样.我们制作源代码的根目录$(topdir),首先我们需要在$(topdir)/builtins/路径下创建一个scarlet.def文件,填写以下内容:$PRODUCESscarlet.c$BUILTINscarlet$FUNCTIONscarlet_builtin$SHORT_DOCscarlet[arg...]将参数作为shell命令执行。将ARGs组合成一个single字符串,将结果用作shell的输入,并执行结果命令。退出状态:返回命令的退出状态,如果命令为空则返回成功。$END*.def格式的文件是bash内置命令的预定义文件,在make过程中,会先将$(topdir)/builtins/mkbuiltins.c文件编译成mkbuiltins,然后使用这个工具将预定义文件转换成*.c格式,再通过gcc编译成*.o文件,最后成为bash可执行文件的一部分。使用mkbuiltins可以显着提高内置命令的编写效率,因为这个工具会自动为你生成大部分重复代码,你只需要配置以下变量:$PRODUCES:用来表示转换源代码的名称target文件,变量的命名不要和其他文件冲突,后面我们会在Makefile.in中配置。$BUILTIN:最后可以调用的内置命令名,命名规则同函数。$FUNCTION:命令的入口函数名,命名规则和函数一样,这个函数的实现也需要在这个文件中。$SHORT_DOC:命令的简要帮助文档,以$END为结束符,这部分内容可以在运行时通过命令--Help来查看。接下来,我们需要在scarlet.def文件中实现scarlet_builtin函数:#include#ifdefined(HAVE_UNISTD_H)#ifdef_MINIX#include#endif#include#endif#include"../shell.h"#include"bashgetopt.h"#include"common.h"intscarlet_builtin(list)WORD_LIST*list;{if(no_options(list))return(EX_USAGE);列表=loptend;返回(l是吗?evalstring(string_list(list),"scarlet",SEVAL_NOHIST):EXECUTION_SUCCESS);}我们首先引用处理输入必要的头文件,主要是用到WORD_LIST这个数据结构,里面存放了bash当前命令的分词结果token,然后通过evalstring解析并执行这些标记。可以看出,我们在编写这个内置命令时只需要处理核心业务逻辑,其他部分可以通过预定义变量的合理配置由mkbuiltins自动生成。接下来,我们需要在Makefile.in文件中配置编译生成的中间文件和引用的头文件:#将$(srcdir)/scarlet.def添加到DEFSRC变量中#添加scarlet.o到OFILES变量中#添加scarlet.o到依赖项区域:scarlet.def#def文件添加scarlet.o:$(topdir)/command.h../config.h$(BASHINCDIR)/memalloc.hscarlet.o:$(topdir)/error。h$(topdir)/general.h$(topdir)/xmalloc.hscarlet.o:$(topdir)/quit.h$(topdir)/dispose_cmd.h$(topdir)/make_cmd.hscarlet.o:$(topdir)/subst.h$(topdir)/externs.h$(topdir)/sig.hscarlet.o:$(topdir)/shell.h$(topdir)/syntax.h$(topdir)/unwind_prot.h$(topdir)/variables.h$(topdir)/conftypes.hscarlet.o:$(BASHINCDIR)/maxpath.h../pathnames.hmkbuiltins处理完所有预定义文件后,还会生成builtins.c和builtext.h文件,我们现在可以重新配置编译bash来测试我们内置命令scarlet的效果:#注意输入$(topdir)/path>makeclean;。/配置;make-j6>./bash>scarlet--helpscarlet:scarlet[arg...]将参数作为shell命令执行。将ARG组合成一个字符串,将结果用作shell的输入,然后执行生成的命令。退出状态:返回命令的退出状态,如果命令为空则返回成功。>VAR=1;POINT+VAR>echo\$$POINT$VAR#bash默认只解释一次变量name>scarletecho\$$POINT1#自定义内置变量实现eval多次解释的功能实现外部命令大多数外部命令都是预编译好的二进制可执行文件,在bash中,每一个都会通过fork-exec方式处理一个外部命令,也就是说外部命令都是在子进程中运行的。本节我们将通过实现ls命令最基本的功能来讲解外部命令的实现过程:#include"apue.h"//这个头文件需要自己去APUE网站下载#include//路径处理相关头文件intmain(intargc,charconst*argv[]){DIR*dp;//路径数据结构structdirent*dirp;//子路径数据结构//判断参数个数是否正确if(argc!=2)err_quit("usage:mylsdirectory_name");//判断传入目录是否可以打开if((dp=opendir(argv[1]))==NULL)err_sys("can'topen%s",argv[1]);//遍历指定目录whilee((dirp=readdir(dp))!=NULL){printf("%s\n",dirp->d_name);}return0;}这个例子来自《Unix环境高级编程》的第1章,实现一条命令至少需要完成输出参数处理、核心业务逻辑、结果输出。还建议在参数输入错误时给出使用提示,在运行出现问题时给出适当的退出状态码。现在我们编译这个命令,把它放到PATH包含的目录下,然后测试这个命令是否能正确输出。>sudogcc1/ls.c-o/usr/bin/myls>myls#使用错误usage:mylsdirectory_name#给出提示>myls.#遍历当前路径。snappekopekoshR.ssh脚本的结构在bash中,脚本是命令和控制逻辑的组合。我们先来看一个常用脚本的例子:#!/bin/bashclearecho"这是mysystem.sh提供的信息,程序现在开始。"printf"你好,$USER\n\n"printf"今天的日期是`date`,这是周`date+"%V"`。\n\n"echo"这些用户当前已连接:"w|剪切-d“”-f1-|grep-v用户|sort-uechoprintf"这是在`uname-m`处理器上运行的`uname-s`。\n\n"echo"这是正常运行时间信息:"uptimeechoecho"这就是所有人!"#!/bin/bash是一个shabang,用来指定这个脚本的解释器/usr/bin/clear是一个外部命令,用来清除当前shell中的输出信息。echo和printf都是bash的内置命令。不同的是echo总是以0状态码退出(退出状态总是success),并且只在标准输出上打印参数,然后打印行尾字符,而printf允许定义一个格式string,并在失败时给出非零退出状态代码。USER是一个变量,用来存放当前用户的名字,需要用$来获取变量的值。脚本的执行方式有两种:source或.:这种方式是在当前shell中执行脚本,相当于用{}包裹的代码块,但需要注意的是这种执行方式可能会污染变量在当前shell中../scriptnameorbashscriptname:这个方法本质上是执行外部变量bash,并将脚本名作为参数传递给命令。这种方法启动的脚本会在子shell中运行,不能访问父shell的全局变量。只能访问环境变量。需要注意的是,使用./scriptname方式执行脚本需要在脚本的第一行配置shabang。我们先来看一个初始化脚本的例子:#!/bin/bashcase"$1"in#脚本的控制逻辑,根据输入'start'选择要执行的分支)#执行cat时servicestarts/usr/share/audio/at_your_service.au>/dev/audio;;'stop')#服务停止时执行cat/usr/share/audio/oh_no_not_again.au>/dev/audio;;esacexit0初始化script(启动脚本)存放在/etc/rc.d/init.d或/etc/init.d目录下,用于启动系统服务,如:syslog服务、电源管理服务、名称和邮件服务。PID=1的初始化进程init读取其配置文件并决定在每个运行级别启动或停止哪些服务。>mvupon-sound/etc/init.d/upon-sound>ln-s/etc/init.d/upon-sound/etc/rc3.d/S99upon-sound#当运行级别为3时调用start>ln-s/etc/init.d/upon-soudecanshund/etc/rc0.d/K01upon-sound#runlevelis0,callstopwhenshutdown参考内容Somethingyoudidn'tknowaboutfunctionsinbashAddingbuiltinsforbash