abbshr.github.io
abbshr.github.io copied to clipboard
Unix Shell中的命令行处理
近期在学习Bash的时候读到了输入/输出和命令行处理部分,我本以为讲的不过是I/O重定向、文件读写、输入输出命令之类的东西。事实并非如此,被我忽视的命令行处理才是文章的精华所在。虽说我以前写过不少Shell脚本,也感觉那东西很简单好学,不过读完这部分我才发现我一直忽略了一个重要问题:“Shell是如何处理你输入的命令行?”。
现在网上的Shell教程很多,但深入学习的很少,即便有也鲜为人知,在此我将这部分重要的内容加上自己的理解记录下来,方便日后查看。
当你敲入一行代码并交给Shell执行时,命令行处理就开始发挥作用了。
管道行
shell从标准输入或脚本中读取的每行称为一个管道行,它包含一个或多个由0个或多个管道字符|
分割的命令。shell会对其读取的每个管道行执行以下操作,也称作命令行处理:
命令行处理流程
这个过程可分为12个阶段:
- 将命令分成由固定字符集分割的记号。这个字符集包含:
空格、制表符、换行符、;、(、)、<、>、|、&
。 - 检测命令的第一个记号,检查是否为不带引号或反斜线的关键字。
- 如果为开放关键字。如
if
等控制结构起始字符串、function
、{
、(
。则该命令为复合命令,shell将读取下一条命令,并重复这一过程。 - 如果不是开放关键字。如
else
、then
、fi
等或逻辑操作符,则shell给出语法错误。
- 如果为开放关键字。如
- 依据别名列表检查每个命令的第一个关键字,如果找到相应匹配,则替换别名定义,并回退到第一步。
- 执行大括号扩展。
- 如果
~
位于单词头,则使用用户的主目录替换~单词
- 对任何以
$
开头的表达式执行变量替换。 - 对
$(string)
进行命令替换 - 计算
$((string))
算术表达式 - 将上三步执行后的部分再次进行单词分割,分隔符使用
$IFS
。 - 对通配符
*
、?
、[
、]
执行路径名扩展。 - 按照
函数 > 内置命令 > $PATH里的脚本
的优先级查找第一个命令。 - 设置完I/O重定向等操作后执行该命令。
这个过程复杂难记,我们可以挑选出容易搞混的几条,把他们的处理优先级记下来:
别名替换、大括号扩展、~
扩展、变量替换、命令替换、算术替换、路径扩展、命令查找
处理流程的改变
上面个的12个步骤其实是可以被改变的,其中引号就可以忽略某些步骤。
引用
- 单引号
'
,忽略前10个步骤,也就是把整个字符串当做一个单词,直接进行命令查找并执行。 - 双引号
"
,只保留6、7、8以及11、12。
命令查找顺序
shell默认的命令查找顺序是:
- 函数
- 内置命令
- 脚本
command
而command
可以忽略别名替换和函数查找:
cd () {
command cd
}
builtin
bulitin
只会查找内置命令。
enable
enable
能屏蔽内置命令:
enable -n cd
处理流程的恢复
内置命令eval
的作用就是重新执行命令行处理,表面上看起来像在程序运行时执行动态生成的字符串。eval
和JavaScript中的eval
一样。
一个使用eval的例子:
list="ls | more"
$list
# 一定会提示找不到那个文件,很明显,管道字符|在第六步才被解析出来,
# 结果|和more都被当成ls的参数了。
# eval这里派上了用场
eval $list
# 被解析的$list将重新进入命令行解析流程,因此会正确执行。