abbshr.github.io icon indicating copy to clipboard operation
abbshr.github.io copied to clipboard

Unix Shell中的命令行处理

Open abbshr opened this issue 10 years ago • 0 comments

近期在学习Bash的时候读到了输入/输出和命令行处理部分,我本以为讲的不过是I/O重定向、文件读写、输入输出命令之类的东西。事实并非如此,被我忽视的命令行处理才是文章的精华所在。虽说我以前写过不少Shell脚本,也感觉那东西很简单好学,不过读完这部分我才发现我一直忽略了一个重要问题:“Shell是如何处理你输入的命令行?”。

现在网上的Shell教程很多,但深入学习的很少,即便有也鲜为人知,在此我将这部分重要的内容加上自己的理解记录下来,方便日后查看。

当你敲入一行代码并交给Shell执行时,命令行处理就开始发挥作用了。

管道行

shell从标准输入或脚本中读取的每行称为一个管道行,它包含一个或多个由0个或多个管道字符|分割的命令。shell会对其读取的每个管道行执行以下操作,也称作命令行处理

命令行处理流程

这个过程可分为12个阶段:

  1. 将命令分成由固定字符集分割的记号。这个字符集包含:空格、制表符、换行符、;、(、)、<、>、|、&
  2. 检测命令的第一个记号,检查是否为不带引号或反斜线的关键字。
    • 如果为开放关键字。如if等控制结构起始字符串function{(。则该命令为复合命令,shell将读取下一条命令,并重复这一过程。
    • 如果不是开放关键字。如elsethenfi等或逻辑操作符,则shell给出语法错误。
  3. 依据别名列表检查每个命令的第一个关键字,如果找到相应匹配,则替换别名定义,并回退到第一步。
  4. 执行大括号扩展。
  5. 如果~位于单词头,则使用用户的主目录替换~单词
  6. 对任何以$开头的表达式执行变量替换。
  7. $(string)进行命令替换
  8. 计算$((string))算术表达式
  9. 将上三步执行后的部分再次进行单词分割,分隔符使用$IFS
  10. 对通配符*[]执行路径名扩展。
  11. 按照函数 > 内置命令 > $PATH里的脚本的优先级查找第一个命令。
  12. 设置完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将重新进入命令行解析流程,因此会正确执行。

abbshr avatar Aug 16 '14 05:08 abbshr