shell解析命令行的过程以及eval命令

本文说明的是一条linux命令在执行时大致要经过哪些过程?以及这些过程的大致顺序。

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/7426371.html

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

时间: 2024-10-07 10:15:44

shell解析命令行的过程以及eval命令的相关文章

在CMD命令行下关闭进程的命令

转载: [重要]在CMD命令行下关闭进程的命令━━━━━━━━━━━━━━━━━━━━━━━━━━ 方法一: 在"运行"中输入:ntsd -c q -pn 程序名字(在MS-Dos中的作用是一样的) 方法二: ntsd使用以下参数杀死进程.c:\>ntsd -c q -p PID 只要你能提供进程的PID,那么你就可以干掉进程. 法二: tskill命令 这个命令与tasklist命令是相对应的吧! tasklist命令是显示有哪些进程正在运行! tskill命令是关闭运行中的进

[编译] 6、开源两个简单且有用的安卓APP命令行开发工具和nRF51822命令行开发工具

星期四, 27. 九月 2018 12:00上午 - BEAUTIFULZZZZ 一.前言 前几天给大家介绍了如何手动搭建安卓APP命令行开发环境和nRF51822命令行开发环境,中秋这几天我把上面篇文章的操作流程全部做成了shell脚本,使得可以让其他人简单运行下脚本.就能够直接建立绿色开发环境,岂不美哉? <[编译] 5.在Linux下搭建安卓APP的开发烧写环境(makefile版)-- 在Linux上用命令行+VIM开发安卓APP> <[编译] 4.在Linux下搭建nRF518

烽火2640路由器命令行手册-01-基础配置命令

基本配置命令 目  录 第1章 系统管理命令... 1 1.1 配置文件管理命令... 1 1.1.1 copy. 1 1.1.2 delete. 2 1.1.3 dir 3 1.1.4 download c0. 3 1.1.5 eraserom.. 4 1.1.6 more. 5 1.1.7 upload c0. 6 1.1.8 download. 6 1.1.9 upload. 7 1.2 基本系统管理命令... 8 1.2.1 boot flash. 9 1.2.2 cd. 10 1.2.

Oracle命令行工具基本操作及SQL 命令

Oracle命令行工具基本操作及SQL 命令 1. 基本概念1.1. 数据类型基本数据类型(NUMBER,VARCHAR2,DATE)O RACEL支持下列内部数据类型:VARCHAR2 变长字符串,最长为2000 字符.NUMBER 数值型.LONG 变长字符数据,最长为2G字节.DATE 日期型.RAW 二进制数据,最长为255字节.LONG RAW 变长二进制数据,最长为2G字节.ROWID 二六进制串,表示表的行的唯一地址.CHAR 定长字符数据,最长为255.2. SQL*PLUS这是

Linux 在一个命令行上执行多个命令(转载)

对于单个命令执行我想大多数人都是明了的,也就是在一个命令行上执行一条命令.那对于在一行上执行多个命令怎么办呢,其实也很简单,只需在各命令之间加上特殊命令符号,我们常规使用到的有3个特殊命令符号. 1. [ ; ] 如果被分号(;)所分隔的命令会连续的执行下去,就算是错误的命令也会继续执行后面的命令. [[email protected] etc]# lld ; echo "ok" ; lok -bash: lld: command not found ok -bash: lok: co

[转]Linux 在一个命令行上执行多个命令

原文链接:   http://blog.sina.com.cn/s/blog_6238358c0100rzvd.html 对于单个命令执行我想大多数人都是明了的,也就是在一个命令行上执行一条命令.那对于在一行上执行多个命令怎么办呢,其实也很简单,只需在各命令之间加上特殊命令符号,我们常规使用到的有3个特殊命令符号. 1. [ ; ] 如果被分号(;)所分隔的命令会连续的执行下去,就算是错误的命令也会继续执行后面的命令. [[email protected] etc]# lld ; echo "o

在Linux终端命令行下播放音乐的命令(Ubuntu)

现在的 Linux 桌面已经发展的很好了,在桌面下播放音乐操作起来也很简单.那么我们还记得在桌面不是那么好的时候我们是怎么播放音乐的么?哎,我是想不起来了,实在是太难了. 不过现在我们可以先安装一个小软件,然后通过命令行来使用这个软件播放音乐,感觉还是很不错滴. 这个软件的名字叫:SOX,支持很多格式的音频文件,如 WAV,MP3,MPG,OGG,FLAC 等等.满足我们日常使用是足够足够的了. 好了,首先,第一步我们需要把它安装到我们的系统里. 1.打开一个终端(Ctrl+Alt+T),然后输

Linux (rz、sz命令行)与本地电脑 命令行上传、下载文件

Linux 与本地电脑直接交互, 命令行上传.下载文件. 一.lrzsz命令行安装: 1.rpm安装:(链接: http://pan.baidu.com/s/1cBuTm2 密码: vijf) rpm -ivh lrzsz-0.12.20-22.1.x86_64.rpm 2.yum 安装: yum install lrzsz 二.命令使用: 1.发送到本地: sz 文件名 2.上传到服务器: rz -be 在弹出的框中选择文件,上传文件的用户和组是当前登录的用户

[转]Windows中的命令行提示符里的Start命令执行路径包含空格时的问题

转自:http://www.x2009.net/articles/windows-command-line-prompt-start-path-space.html 当使用Windows 中的命令行提示符执行这段指令时(测试Start命令执行带空格的路径的程序或文件问题),第一行Start会成功执行,跳出记事本程序,而第二行,会 Start跳出一个新的命令提示符,标题上写着路径,但是不会执行任何命令,第三行Start命令行提示符会提示C:\Program文件不存在,提示无 法执行. start