shell解析命令行的过程以及eval命令
bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
本文说明的是一条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:--
longshuai's age is 28
(1).读取输入的命令行。
(2).解析引用并分割命令行为各个单词,各单词称为token。其中重定向所在的token会被保存下来,直到扩展步骤(5)结束后才进行相关处理,如进行扩展、截断文件等。
shell中有3种引用方式:反斜线引用、单引号引用和双引号引用。
◇ 反斜线转义:使得元字符变为普通的字面字符。但这只能对反斜线后一个字符进行转义。
◇ 单引号引用:单引号内的所有字符全部变为字面符号符号。但注意:单引号内不能再使用单引号,即使使用了反斜线转义也不允许。
◇ 双引号引用:使双引号内所有字符变为字面符号,但"\"、"$"、"`"(反引号)除外,如果开启了"!"引用历史命令时,则感叹号也除外。
解析引用后,于是就可以将命令行进行单词分割,分割后的每一部分都称为一个token。分隔时,不仅分割单个命令,还分割命令列表,所以分隔符包括:空格、tab、分号、管道符号、&、&&、||、重定向符号、圆括号等。
于是上述命令分割为以下几个token:
(注:虽然换行符是shell划分命令行token的元字符,但是shell并不认识这里的\n,shell只认识我们直接手动敲下的回车键,而这里\n是由echo -e选项识别的换行符,因此这里的\n不是shell解析命令行时划分token的换行符,于是\n也被认为是双引号包围的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。
④算术扩展:计算算术值,并将计算结果替换到对应位置处。例如$((a+4))替换为28。
⑤命令替换:此过程将执行命令替换中的命令,并将结果替换到token的对应位置处。
⑥进程替换:将进程的执行结果替换到对应位置。类似于命令替换。替换格式为"<(cmd_list)"和">(cmd_list)",例如 cat <(cat /etc/hosts) 。redhat系列应该都支持进程替换。
经过以上几种扩展后,得到如下结果:
⑦单词拆分:扫描变量扩展、命令替换和算术扩展的结果,对非引号内的结果按照$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进行解析、执行。
执行过程如下图所示:
使用示例来说明:
[root@xuexi ~]# a=;name='long$a' # 注意,使用的是单引号,禁止$a被扩展
如果直接执行 echo $name ,则结果为"long$a",但如果执行 eval echo $name ,结果将是"long24"。
[root@xuexi ~]# 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解析时被当作特殊变量"$$"被扩展。
[root@xuexi ~]# a=b
[root@xuexi ~]# b=haha [root@xuexi ~]# eval echo \$$a
haha
注:本文并非一定准确,只是根据man bash总结而来。如有错误,请明确指出。多谢
shell解析命令行的过程以及eval命令的更多相关文章
- (转)shell解析命令行的过程以及eval命令
shell解析命令行的过程以及eval命令 本文说明的是一条linux命令在执行时大致要经过哪些过程?以及这些过程的大致顺序. 1.1 shell解析命令行 shell读取和执行命令时的大致操作过 ...
- 在CMD命令行下关闭进程的命令
转载: [重要]在CMD命令行下关闭进程的命令━━━━━━━━━━━━━━━━━━━━━━━━━━ 方法一: 在"运行"中输入:ntsd -c q -pn 程序名字(在MS-Dos ...
- [编译] 6、开源两个简单且有用的安卓APP命令行开发工具和nRF51822命令行开发工具
星期四, 27. 九月 2018 12:00上午 - BEAUTIFULZZZZ 一.前言 前几天给大家介绍了如何手动搭建安卓APP命令行开发环境和nRF51822命令行开发环境,中秋这几天我把上面篇 ...
- C# “预先生成事件命令行”和“后期生成事件命令行”
概述 Visual studio 项目允许在项目属性生成事件一栏中指定预先生成和后期生成事件来实现项目生成与部署的自动化. 实例1: 我自己写了一个调试工具,该工具处于一边开发一边使用过程中.实际工作 ...
- linux 命令行远程登录 后台运行命令的方法
linux 命令行远程登录 后台运行命令的方法 http://blog.csdn.net/isuker/article/details/55061595 Linux 技巧:让进程在后台可靠运行的几种方 ...
- xcode命令行编译时:codesign命令,抛出“User interaction is not allowed.”异常 的处理
之前正常运行的hudson iOS编译服务器slave节点,忽然出现编译失败.发现原因有2个: 第一个原因是编译机上用来签名的用户帐号过期,第二个原因是操作系统和xCode升级造成的. 对于第一个,重 ...
- Linux 在一个命令行上执行多个命令
Linux 在一个命令行上执行多个命令 1. [ ; ] 如果被分号(;)所分隔的命令会连续的执行下去,就算是错误的命令也会继续执行后面的命令. 2. [ && ] 如果命令被 &am ...
- Linux 在一个命令行上执行多个命令(转载)
对于单个命令执行我想大多数人都是明了的,也就是在一个命令行上执行一条命令.那对于在一行上执行多个命令怎么办呢,其实也很简单,只需在各命令之间加上特殊命令符号,我们常规使用到的有3个特殊命令符号. 1. ...
- VS2010-使用“预先生成事件命令行”和“后期生成事件命令行”功能
原文:VS2010-使用"预先生成事件命令行"和"后期生成事件命令行"功能 xcopy /r /y $(TargetPath) $(ProjectDir)..\ ...
随机推荐
- python学习笔记——列表操作
python列表操作——增 append:追加一条数据到列表的最后 name = ["Zhangsan","XiongDa","Lisi"] ...
- js几秒以后倒计时跳转示例
代码如下: <html> <head> <title>出错啦~~~</title> <link href="css/login1.css ...
- [CF787D]遗产(Legacy)-线段树-优化Dijkstra(内含数据生成器)
Problem 遗产 题目大意 给出一个带权有向图,有三种操作: 1.u->v添加一条权值为w的边 2.区间[l,r]->v添加权值为w的边 3.v->区间[l,r]添加权值为w的边 ...
- 有关conv_std_logic_vector和conv_integer
原文地址:关于conv_std_logic_vector 和 conv_integer 这两个函数的使用问题作者:xiphosura std_logic_arithThis is the librar ...
- 集合的定义,操作及运算 (Python)
集合的定义: 集合和列表([ ]) 与 字典 ( { }) 不同,没有特别的特别的语法格式.可以使用set () 创建. 集合同字典一样是无序的.也是不具有重复性的.因此可以把列表变成集合进 ...
- log4j(七)——log4j.xml简单配置样例说明
一:测试环境与log4j(一)--为什么要使用log4j?一样,这里不再重述 二:老规矩,先来个栗子,然后再聊聊感受 (1)这里栗子有一点特别呀!给出了包名唉!想必有用,是的,配置文件中要特别说明一下 ...
- (转)mybatis:动态SQL
概述:在mybatis中,动态语句是个非常强大和灵活的功能,并且动态语句可以放在sql的任何地方,利用该功能,我们可以写出非常灵活的代码.在mybatis的动态语句中常常可能会用到以下几个运算和逻辑判 ...
- (转)Synchronized(对象锁)和Static Synchronized(类锁)的区别
场景:面试的时候经常用得到! 1 综述 Synchronized和Static Synchronized区别 一个是实例锁(锁在某一个实例对象上,如果该类是单例,那么该锁也具有全局锁的概念),一个是全 ...
- Java内存区域与对象创建过程
一.java内存区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则 ...
- myeclipse项目部署到idea常见问题
由于myeclipse是付费产品,经过几次破解不成功后,遂弃之,转投IntelliJ IDEA门下.但这就出现一个问题了,以前用的eclipse.myeclipse以及spring tools sui ...