必须要知道命令解析过程的意义在于:能够清楚的知道命令解析步骤。若发生错误时,能够知道该怎样更改。
比如在博客:I/O重定向(点击打开链接)的举例:例①中,就有因为不知道命令行是如何读取的,而造成while语句块和其后的重定向,对结果的不解。
再比如博客:变量引用(点击打开链接)中第三大点的第3小点,变量举例中就有很多是对命令行的解析过程和如何解析进行的举例分析。如:a=var;b 这一句,是读完a=var;b然后才开始划分一条命令呢,还是先读a=var;再读b呢。还有:a=1 2 3又会怎样解析呢,是读完a=1 2 3,还是只读取:a=1? 读完命令后,这一句应该怎样划分呢?
再比如重定向:>$a (注:$a是变量引用,是一个文件名。这一句即是输出重定向到文件)。这一句的解析,是先重定向>,再$a变量解析呢;还是先$a变量替换,再重定向>呢。很显然这两种是截然不同的。这些都是命令解析过程中的问题。
1、读取命令行
2、命令历史替换
3、别名替换
4、花括号扩展
5、波浪号替换
6、I/O重定向
7、变量替换
8、命令替换
9、单词解析
10、 文件名生成
11、引用字符处理
12、进程替换
13、环境处理
14、执行命令
15、跟踪执行过程
⑴第一步shell会读取完整的命令行
分为两类 (读命令行和读结构语句):
①、读取命令行
Shell会逐个判断读取的每一个字符,直至遇到一个分号“;”、后台进程符号“&”、逻辑与“&&”、逻辑或“||”或换行字符等。整个命令行的读取过程才算结束。
②、读取一个结构语句
在读取一个结构语句,比如,if语句块、for语句块、while语句块等,shell将会读入整个结构语句。直至遇到上面“;”、“&”、“&&”、“||”或换行符。
所以,如果语句块后面有重定向> <这些,也会被当做一部分,一起被读入。
⑵ 第二步对命令语句进行语法分析
执行的操作:
在读入一个完成的命令或结构语句之后,shell开始对命令语句进行语法分析,把命令语句分解为一系列单词或关键字。
注意:通常,shell假定每个“单词”或“关键字”都是以空格或制表符分隔的一系列“连续字符”组成的。甚至是“<” “>” “|” “^”等特殊字符组成的单字。
分析:从命令历史中读出相关的命令,用于替换命令行中需要进行替换的命令。
比如:!!命令,shell会从命令历史记录中取出相应命令,去替换!!
注意:命令历史替换,在非交互式shell(如shell脚本)中并不适用。即,在shell脚本中不要使用历史命令等相关功能。
分析:如果你用过aliases命令进行过命令替换操作。在命令行中,会首先对别人命令进行替换。
分析:花括号提供了一种简单的文件名生成方法
比如下面的三个命令:
① mkdir ~/scripts/{old,new,tools,admin} #扩展文件名
一次性在同一个文件夹下创建四个文件
② cp /home/book/src/{main,list.scan.mon}.c #扩展文件名
一次性复制/home/book/目录下的四个文件,到当前目录
③ echo char-{one,two,three,four} #扩展字符串
一次性显示char-one、char-two、char-three、chat-four四个字符串
注意:花括号扩展机制,在非交互式shell(shell脚本)中被禁用。
波浪号是用于指定用户的主目录。
问1:上面这句话是什么意思呢?
答:比如你用book用户登陆了linux系统,“~”就代表/home/book,如果你用xiaoming用户登陆linux系统,“~”就代表/home/xiaoming。
不管你在哪个目录下,~均代表当前登录活动用户的主目录。
问2:用户主目录有几种查看方式呢?
三种查看方式:
一是:echo $HOME
二是:echo ~用户名 比如:echo ~book
三是:echo ~
波浪号在命令中的替换:
如果在你的命令中有波浪号,则会进行替换操作,
① 命令:ls –l ~
表示列出用户目录下的所有文件
② 命令:ls –l ~/music
表示列出用户主目录下的music目录中的所有文件,或列出主目录下的music文件
注意:如果~后面不为空,但是接的也不是一个有效地用户名或路径,则shell不会做任何替换。
如命令:echo ~xx
输出:~xx
这一步将会进行输入/输出重定向的操作。
以标准输出重定向进行讲解
执行的操作:
① 关闭指定的文件描述符;关闭文件描述符1。
② 打开重定向的文件,并保存打开这个文件的文件指针信息
③ 把这个文件指针信息复制到原标准输出所在文件指针数据结构表中已腾出的位置。
标准输入、标准错误输出重定向执行的过程 与 标准输出重定向执行的过程是一样的。
涉及的变量:内部变量、用户自定义变量、命令行参数的引用、位置参数的引用
含义:把这些变量引用,替换成它原本的样子^
命令替换表达式是由反引号或$(…)形式的命令语句组成的。命令替换就是替换成命令表达式的值 或 对管道符前面的命令进行的命令替换。
命令替换表达式中的命令,可以是任何Linux命令,比如组合命令,管道符号的并列命令,重定向等等,没有任何限制。
命令替换表达式中的命令作为命令语句,可以使用任何shell机制,即,命令执行时,也是完全按照命令行解析过程来展开执行的。
按照分隔符对命令行进行拆分,分离出最基本的命令行元素或单词。
分隔符一般为空格、制表符和换行符。当然,你也可以修改环境变量IFS来增加一些特殊情况下的需要。如果有单、双引号,则内部的字符作为一个整体,作为一个单词。
IFS的设置:IFS=“$IFS[后面跟一个你设定的特殊字符]”
比如:
此时,冒号“:”就被当做一个分隔符来处理了。
不过不要把你自己设定的分隔符直接用在命令关键字,比如:echo后面,当做分隔符,会报错。自己设定的分隔符只对变量有效,比如从文本中读的变量,这种是自己设定的分隔符用处最多的地方。
即扩展元字符。https://blog.csdn.net/huayangshiboqi/article/details/79964711
这一步,会对上一步解析出来的每一个单词,逐一检索,以检查单词中间是否包含任何符合文件名生成规则的元字符。比如:* ? 等等。
包含元字符的每一个基本单词,shell将会尝试匹配当前目录或指定目录中的文件名,以试图进行扩展。
双引号中的*不解释:双引号的用法 而echo *则会进行扩展。
删除引用符号,包括转义字符、单、双引号等引用字符。
在前十步中,所有的扩展,包括变量替换、命令替换、文件名生成等等的操作已经全部结束了。但是,在这个前十步中,转义字符、单引号、双引号中的部分元字符是不能被引用的,也是一直被保留到第11步,直到前十步结束,这些元字符不会再解析的时候,把这些元字符按照原样放在字符串的中间。(因为前十步已经结束了,所以这里可以安心的删除单引号、双引号、转义字符等,因为元字符不会再进行解析)
对转义字符,单双引号等引用字符进行删除操作。
进程替换形式:>(command)或<(command)
进程替换出现位置:命令中一个文件名参数所处位置
进程替换的作用:实现进程间标准输入与标准输出的交互通信
进程替换机制:有一个/dev/fd目录下的两个特殊文件,作为进程间通信的中间文件。比如command1 >(command2),则是把command1的标准输出写到/dev/fd目录下的一个文件,然后把该文件重定向到command2,作为标准输入。
举例:
① 比较两个文件的差异
diff <(ls -l testfile1) <(ls -l testfile2)
注意的是,进程替换形式是出现在文件名参数该出现的位置
② 排序
sort -n -k 5 <(ls -l)
③ 一种错误的用法
ls -l >(grep test)
用法错误。
本意是想把ls -l 的标准输出作为grep命令的标准输入,并抓取中间含有字符串test的部分。
但是进程替换形式的位置使用错误,进程替换形式只能出现在文件名参数所处的位置。
这里可以改为管道符,或者是grep test <(ls -l)
注意:尽管进程替换的执行较后,但并不意味着是在前面所有步骤全部执行完了,才开始解析。
比如:如果你认为进程替换执行在去掉双引号之后,那么双引号中可以随便使用进程替换,这一观点明显是错的。
脚本举例test.sh:
a=test
grep "${a}" <(echo /home/book/test*)
调试运行:bash -x test.sh
输出信息:
+ a=test
+ grep test /dev/fd/63
++ echo /home/book/test /home/book/test.sh
/home/book/test /home/book/test.sh
分析:脚本中的第二句,分了两个命令来执行。
首先是grep test /dev/fd/63。其中test是”${a}”的替换,/dev/fd/63是进程替换过程中的特殊文件。
从这一步就可以看出,变量替换已经完成、双引号已经被删除,但是进程替换并没有执行。
然后是执行进程替换echo /home/book/test /home/book/test.sh,然后把输出结果写到/dev/fd/63中,
最后是执行grep命令。
这中间有一个非常重要的东西:/dev/fd/63.
所以,既然双引号在进程替换之前就被删除了,能不能把进程替换写到双引号里面?
答案是:不可以!
因为在双引号解析之前,进程替换会有一个标志性的文件/dev/fd/63。如果进程替换被写到了双引号内,进程替换被当做一串普通的字符串,而不会有/dev/fd/63中间文件的产生。
所以在后一步,也不会把这一串普通字符串当做进程替换来执行。
其实就是搜索命令。
根据环境变量PATH中的路径去检索命令文件的存储位置,然后以命令文件的完整路径名替换命令行中的命令名。
执行的命令分为四种:Linux命令、Shell内部命令、应用程序、shell脚本。
① 如果是一个普通的Linux命令,Shell将会启动一个单独的子进程执行相应的命令;
② 如果是shell内部命令,由shell直接执行。
③ 如果是应用程序,则shell子进程将调用exec系统调用,来执行应用程序。
④ 如果是shell脚本,将由shell解释器执行文件中的每一行语句。
怎样分辨应用程序和shell脚本:
子进程会尝试将命令文件加载到内存中,然后开始执行。如果无法加载到进程,紫禁城就嘉定相应的命令文件是一个Shell脚本,因而由Shell采用解释执行的方式执行文件。
调试。
如果在命令中使用set命令的-v或-x等选项(用于调试的命令),则会跟踪命令的执行过程。Shell将会输出当前正在执行的语句。
此时输出的命令语句的特点:
① 实际执行的命令。是在进行命令替换之后,实际执行的命令
② 按照命令的逻辑顺序依次输出