shell如何解析命令行以及eval命令

1.1 shell解析命令行

shell读取和执行命令时的大致操作过程如下图:

以执行以下命令为例:

echo -e "some files:" ~/i* "\nThe date:$(date +%F)\n$name‘s age is $((a+4))" >/tmp/a.log

假设在执行该命令前,已赋值变量"name=longshuai"和"a=24",于是重定向到/tmp/a.log中的结果为:

some files: /root/inotify.sh /root/inotify.sh.ori
The date:2017-08-14
longshuai‘s age is 28

(1).读取输入的命令行。

(2).解析引用并分割命令行为各个单词,各单词称为token。其中重定向所在的token会被保存下来,直到扩展步骤(5)结束后才进行相关处理,如进行扩展、截断文件等。

shell中有3种引用方式:反斜线引用、单引号引用和双引号引用。

◇ 反斜线转义:使得元字符变为普通的字面字符。但这只能对反斜线后一个字符进行转义。

◇ 单引号引用:单引号内的所有字符全部变为字面符号符号。但注意:单引号内不能再使用单引号,即使使用了反斜线转义也不允许。

◇ 双引号引用:使双引号内所有字符变为字面符号,但"\"、"$"、"`"(反引号)除外,如果开启了"!"引用历史命令时,则感叹号也除外。

解析引用后,于是就可以将命令行进行单词分割,分割后的每一部分都称为一个token。分隔时,不仅分割单个命令,还分割命令列表,所以分隔符包括:空格、tab、分号、管道符号、&、&&、||、重定向符号、圆括号等。

于是上述命令分割为以下几个token:

如果分割时发现了管道符号,或者是命令列表等组合了多个命令的情况,则每个命令都的token都相互独立。

(3).检查命令行结构。主要检查是否有命令列表、是否有shell编程结构的命令,如if判断命令、循环结构的for/while/select/until,这些命令属于保留关键字,需要特殊处理。

(4).对第一个token进行别名扩展。如果检查出它是别名,则扩展后回到(2)再次进行token分解过程。

(5).进行各种扩展。扩展顺序为:大括号扩展;波浪号扩展;参数、变量和命令替换、算术扩展(如果系统支持,此步还进行进程替换);单词拆分;文件名扩展。

不同引号的引用方式,将改变扩展的起始步骤,正如上图所画,没有任何引号时将从头到尾全部扩展,使用单引号时将完全不会进行任何扩展,使用双引号时将从变量替换开始继续扩展。

①大括号扩展:如/tmp/{a,b}.log扩展为/tmp/a.log和/tmp/b.log。

②波浪号扩展:扩展为家目录。如root用户下的~/.ssh扩展为/root/.ssh。

③变量扩展:即操作和替换变量值。如$a替换为它的值24,${name:-longshuai}替换为longshuai。

④命令替换:此过程将执行命令替换中的命令,并将结果替换到token的对应位置处。

⑤进程替换:将进程的执行结果替换到对应位置。类似于命令替换。替换格式为"<(cmd_list)"和">(cmd_list)",例如"cat <(cat /etc/hosts)"。redhat系列应该都支持进程替换。

⑥算术扩展:计算算术值,并将计算结果替换到对应位置处。例如$((a+4))替换为28。

经过以上几种扩展后,得到如下结果:

⑦单词拆分:扫描变量扩展、命令替换和算术扩展的结果,对非引号内的结果按照$IFS的值对这些结果进行单词分割。

注意,如果没有进行扩展,或者扩展结果使用引号包围了,则不会进行此步的单词拆分。

默认情况下,$IFS值为"   \t\n",所以扩展结果中每遇到空格、制表符、换行符都将被分割为两个单词。

这一步其实很容易犯错,典型的是test命令。例如变量name="Ma longshuai",则test $name == "longshuai"将报错,因为变量扩展后该语句变为test Ma longshuai == "longshuai",由于是变量替换,所以随后进行单词拆分,使得Ma和longshuai被拆分为两个单词,但实际上它们共同组成变量name的值。

所以,为了正确操作变量替换和命令替换,尽量将它们使用引号包围。例如test "$name" == "longshuai",这时将不会进行单词拆分。

⑧文件名扩展:对每个token进行搜索,将搜索"*"、"?"和"["符号,搜索到了将进行文件名扩展。例如将上面的"/root/i*"扩展为"/root/inotify.sh /root/inotify.sh.ori"。

(6).引号去除。经过上面的过程,该扩展的都扩展了,不需要的引号在此步就可以去掉了。

所以得到如下结果。

(7).搜索和执行命令。

单词分割后,复杂的命令行将由各个简单命令结构组成。于是可以搜索每个简单命令结构的第一个token中的命令,同时还带有一系列命令选项。例如上面的"echo"和"-e"。

如果命令中不含任何斜杠:

①则先判断是否有此名称的shell function存在,如果有则调用它,否则进行下一步搜索。

②判断该命令是否为bash内置命令,如果是则执行它,否则进行下一步搜索。

③从$PATH的路径下搜索该命令,如果搜索到了,则执行,否则报错。

如果命令中包含一个或多个斜杠,则进行相对路径扩展、绝对路径查找,找到了则执行,否则报错。

(8).返回退出状态码。

1.2 eval命令

正常情况下,当搜索到命令时将会执行命令,但如果搜索到的命令为eval时,则处理方式有所不同。

它的语法格式为:

eval command arguments

按照前文所述shell解析过程,将最终得到eval command和一系列扩展后的选项、参数,当搜索命令时,搜索到的结果为eval命令,于是eval命令将除了eval命令(以及eval的选项)的所有token再次传递给shell进行二次解析。但重定向所在token除外,因为重定向token早已被shell保存下来,所以不会再次截断文件。

也就是说,"command arguments"被当作eval命令的参数,被传递给shell进行解析、执行。

执行过程如下图所示:

使用示例来说明:

[[email protected] ~]# a=24;name=‘long$a‘     # 注意,使用的是单引号,禁止$a被扩展

如果直接执行echo $name,则结果为"long$a",但如果执行eval echo $name,结果将是"long24"。

[[email protected] ~]# eval echo $name
long24

首先shell按照正常过程解析,在变量替换时由于使用了单引号,所以$name第一次变量替换的结果为"long$a",直到命令搜索时发现搜索到的命令是eval命令,执行eval命令,该命令将其参数"echo long$a"再次传递给shell,相当于在标准输入中输入了"echo long$a",于是shell进行二次解析,这次的变量替换将$a替换为24,最后搜索命令发现是echo命令,于是最终得到"long24"。

关于eval,更多的用法是间接变量$$var的用法,在bash shell中需要在第一个$前加上反引号,即\$$var,这么做的原因是显然的:防止第一次shell解析时被当作特殊变量"$$"被扩展。

[[email protected] ~]# a=b
[[email protected] ~]# b=haha

[[email protected] ~]# eval echo \$$a
haha

注:本文并非一定准确,只是根据man bash总结而来。如有错误,请明确指出。多谢

回到系列文章大纲:http://www.cnblogs.com/f-ck-need-u/p/7048359.html

转载请注明出处:http://www.cnblogs.com/f-ck-need-u/p/7419502.html

注:若您觉得这篇文章还不错请点击下右下角的推荐,有了您的支持才能激发作者更大的写作热情,非常感谢!

时间: 2024-12-16 16:09:51

shell如何解析命令行以及eval命令的相关文章

Shell下的命令代换与eval命令

在shell中,如果想获得一个命令的返回值,如果为整数,可以直接用$?获取其退出码. 但如果一条命令的返回值不是整数,怎么实现? 不得不提shell下的命令代换功能! 两种方式实现命令代换 假设我们某个变量需要获得当前时间,则可以这样实现: date=`date` echo $date 或者也可以这样 date=$(date) echo $date 这两种方式都可以实现命令代换的功能,那么他们有何不同? 两种命令代换方式的区别 1.$()看起来比较简洁,而``很容易与''进行混淆. 2.在多层替

Linux一步步学习(一)命令行下执行命令

因为之前申请了阿里云服务器(免费体验半年),所以刚好用阿里云的服务器安装了个Ubuntu12.04 64位PHP的运行环境 本次主要对基础命令行的总结: (1)显示日期与实践的命令:date (2)显示日历的命令:cal (3)简单好用的计算器:bc (4)重要热键[tab],[ctrl]-c,[ctrl]-d (5)man page与info page (6)超简单文本编辑器:nano (7)惯用关机命令:shutdown 开始学习: (1)显示日期与实践的命令:date 直接输入date,则

处理命令行参数的命令

#include<stdlib.h> #include<stdio.h> #define TURE 1 void process_standered_input(void); void process_file(char* filename); int option_a,option_b; /*处理命令行参数的命令*/ //类似于prog -a -b -c name1 name2 name3的命令行输入参数命令 int main(int argc,char **argv) { wh

shell命令行快速编辑命令

ctrl r:命令行出现 reverse-i-search,输入字符将在输入历史中匹配命令 ctrl p:向前翻看历史 ctrl n:向后翻看历史 ctrl a:命令行首 ctrl e:命令行尾 ctrl f:向前跳转一个字符 ctrl b:向后跳转一个字符 ctrl w:删除前一个词 ctrl u:删除至行首 ctrl k:删除至行尾 ctrl d:删除当前字符 ctrl y:粘贴最后一次删除的字符

windows命令行工具常用命令

windows常用命令 打开"运行"对话框(Win+R),输入cmd,打开控制台命令窗口... 也可以通过cmd /c 命令 和 cmd /k 命令的方式来直接运行命令 注:/c表示执行完命令后关闭cmd窗口:/k表示执行完命令后保留cmd窗口 # 控制台命令窗口中一些技巧 复制内容:右键弹出快捷菜单,选择“标记(K)”,然后选中所需复制的内容,然后右键即可 粘贴内容:右键弹出快捷菜单,选择“粘贴(P)” 在文件夹空白处按住Shift,然后右键弹出快捷菜单,可以看到“在此处打开命令行窗

linux常用命令-命令行编辑,history,命令行快捷键,pstree,alias,命令替换,通配符

命令行编辑:光标跳转: Ctrl+a:跳到命令行首 Ctrl+e:跳到命令行尾 Ctrl+u: 删除光标至命令行首的内容 Ctrl+k: 删除光标至命令行尾的内容 Ctrl+l: 清屏 Ctrl+d: 删除光标后面内容 命令历史:查看命令历史:history -c:清空命令历史 -d OFFSET [n]: 删除指定位置的命令 -w:保存命令历史至历史文件中 环境变量PATH:命令搜索路径HISTSIZE: 命令历史缓冲区大小 命令历史的使用技巧:!n:执行命令历史中的第n条命令: [[emai

esxi开启命令行模式以及命令开启虚拟机

在esxi界面按F2登录选择troubleshooting options选择enable esxi shell返回登录时的界面然后按alt+F1 从命令行启动虚拟机:1.用命令列出虚拟机的ID:vim-cmd vmsvc/getallvms |grep <vm name> 此命令输出的第一行为vmid2.用命令查看虚拟机启动状态:vim-cmd vmsvc/power.getstate <vmid> 3.用命令启动虚拟机:vim-cmd vmsvc/power.on <vm

K8s之kubectl命令行工具常用命令

kubectl管理 Kubectl是管理k8s集群的命令行工具,通过生成的json格式传递给apiserver进行创建.查看.管理的操作 注意:此处需要用到我们之前部署的K8s多节点的部署环境,如果还未部署的可以参考我的上篇文章:https://blog.csdn.net/JarryZho/article/details/104212822 常用命令行: `查看帮助命令` [[email protected] ~]# kubectl --help kubectl controls the Kub

Mac系统终端命令行不执行命令 总出现command not found解决方法

配置过安卓开发环境,改过bash_profile这个文件,最后不知怎么的只有cd命令能执行,我猜测可能修改bash_profile文件后没有保存 导致的     保存命令是:  source .bash_profile 说下我的解决方法: 1,在命令行中输入: export PATH=/usr/bin:/usr/sbin:/bin:/sbin:/usr/X11R6/bin 这样可以保证命令行命令暂时可以使用.命令执行完之后先不要关闭终端. 2,输入 cd ~/ 进入当前用户的home目录. 3,