Shell:通过实例理解Bash命令替换和命令行处理流程

2018年05月22日 2161Browse 3Like 0Comments

前言

前几天我在浏览一个论坛看到某个网友发了一个问题求助帖,具体链接暂时没找到。他敲了很多非常接近的命令。

$date
Mon May 21 12:21:47 CST 2018
$date;
Mon May 21 12:21:49 CST 2018
$`date`
-bash: Mon: command not found
$`date;`
-bash: Mon: command not found
$echo date
date
$echo "date"
date
$echo "date;"
date;
$echo `date`
Mon May 21 12:22:15 CST 2018
$echo `date;`
Mon May 21 12:22:21 CST 2018
$`echo "date"`
Mon May 21 12:23:04 CST 2018
$`echo "date;"`
-bash: date;: command not found

发现有些命令可以正确执行,而有些命令却因为加了分号(;)双引号("")或反引号(``)不能执行或没有达到预期的运行结果,他感到非常迷惑,也搞不清楚原因,在论坛上问来问去也没有得到答案。问题大致是这样的:

  1. 对于`date`和`date;`为什么在反引号内对date命令加不加分号都能执行?
  2. 对于echo "date" 和echo "date;",在对date指令用双引号而不用反引号的情况下,为什么执行不了?而改成反引号 $echo `date`或$echo `date;` 就可以执行了?
  3. 对于`echo "date"`和`echo "date;"`,为什么跟上面不加反引号的 echo "date" 和 echo "date;" 执行结果大不相同?
  4. 对于`echo "date;"`,为什么在date后加了分号就执行出错,而不加分号的`echo "date"`就可以执行?

上面这些命令行确实让人迷惑,反引号,双引号所加的位置稍有不同,Bash对其执行的结果却不相同。原因究竟是什么呢?

相关的基础知识

首先,要搞清楚这几个问题,就要理解Shell中处理命令行的这三个方面:命令替换的过程、双引号的作用、Shell对命令行中的解析和执行顺序。

1. 命令替换:Bash用两个反引号``或$()来做命令替换,这里仅说反引号。命令替换是指Bash将反引号所包含的字串视作单独的命令,用其执行的结果替换掉原来命令行中反引号及其内部的字串。所以,反引号的内容必须是语法正确的命令才能确保正确实现命令替换。
2. 双引号""引用:双引号是用来关闭其包含的字串的、将其内部包含的内容进行普通化。例如,当双引号内是一个或多个命令,则变成了一个普通字串;它也关闭具有一些特殊功能的字符(meta),例如 | 等在不加引号的情况下表示管道,若加了双引号,| 就起不到管道的作用,而变成了一个无特殊功能的普通字符。双引号中只有$、`、\三个特殊字符不会被关闭。
3. Bash对命令行的解析和执行顺序:当命令行输入完成后(敲了回车键),Bash会读取整个命令行,宏观来看Bash将对命令行依次进行:解析、扩展和执行三大步骤。首先,Shell将整个命令行分解成单词(双引号和反引号内部的内容会被视作成一个单词),然后再从左至右对各个单词进行词法/语法分析,若遇上有扩展/替换时进行留后处理。若分析没有发现错误,则进行各种留后处理(第二步,如进行各种扩展和替换),命令替换就是其中的一种。当各种扩展和替换完成后再去执行命令(第三步)。

命令行实例分析

有了对这三个方面的认识,就不难理解有些命令为什么能够正确执行,有些却执行不了。现在对这些命令行逐个进行解释。

下面两个命令行:第一个不解释。第二个命令行date后面的分号用来分割多个命令,只是此处分号后面没有命令,但语法无误,所以是先执行了分号前的date命令,然后没有命令可以执行。所以,这两个命令行的运行结果是一致的。

$date
Mon May 21 12:21:47 CST 2018
$date;
Mon May 21 12:21:49 CST 2018

下面两个命令行:整个命令行直接被``包含了、而没有其他单词,所以仅需要的就是进行命令替换。先执行反引号内部的date指令,输出了日期:Mon May 21 12:21:47 CST 2018,然后将`date`替换成刚才输出的:Mon May 21 12:21:47 CST 2018。这样就完成了命令替换,此时,Bash将替换后的新内容作为一个新的命令来执行(不再做词法和语法分析),但是Bash发现搜索不到以Mon开头的命令或程序,所以出现下述错误提示。

$`date`   
-bash: Mon: command not found
$`date;`
-bash: Mon: command not found

下面三个命令行:尽管单独的date是一个获取日期时间的可执行程序,但此处Bash会将其解析成echo的参数;采用了双引号将date关闭,那么未加引号或双引号内的内容 date 或 date; 就不会成为一个可执行的命令,而是一个普通的字串作为echo的参数而已。所以,echo 直接输出加/未加 双引号中的字串。

$echo date
date
$echo "date"
date
$echo "date;"
date;

下面两个命令行:在执行整个命令行之前需要做的就是进行命令行内的命令替换。替换完了再执行整个命令行。所以,echo 输出的是 date或date; 命令已执行的结果(日期时间信息)。

$echo `date`
Mon May 21 12:22:15 CST 2018
$echo `date;`
Mon May 21 12:22:21 CST 2018

下面两个命令行:整个命令被反引号包围,所以,依然先进行命令替换,然后再执行替换后的命令(如替换后的内容语法无误,则会正确执行,反之亦然):echo "date" 将输出 date,用 date 替换 `echo "date"` 完成命令替换。替换完成再执行的是 date 命令,所以可以正确输出 Mon May 21 12:23:04 CST 2018。同样,这里的第二个命令行先进行命令替换后得到 date; 这个内容,Bash 去执行命令前,发现找不到 date; (带分号的)为可执行的命令或程序,因为date;(带分号的)是一起被当做一个单独的命令来执行的,所以出错了。因为此例中Bash在完成命令替换后,对命令行已经完成了最初的词法/语法解析步骤,不会再去将解释date; 。

$`echo "date"`
Mon May 21 12:23:04 CST 2018
$`echo "date;"`
-bash: date;: command not found

使用eval

那么,有能让`echo "date;"` 这个命令行正确执行的方法吗?当然有!那就是使用 eval。

$eval `echo "date;"`
Mon May 21 12:28:02 CST 2018

使用eval的作用就是让Bash在本应去进行最后一步(执行命令)时重新从头开始将当前Bash已解析和扩展的命令行(例如命令替换后的内容)再去进行一次解析、扩展/替换之后,最后才去执行。所以,其具体过程是这样的:

  • 第一次解析和扩展/替换之后的内容是:date;,完成后Bash不是去执行该内容,而是再去做第二次解析和扩展/替换。
  • 进行第二次解析和扩展/替换:Bash此时会认为命令行输入的是date;,将重新解析date;。所以,命令行将被解析为:有一个date指令先执行,然后还有一个分号后其他的指令需要执行(实际是空的)。没有发现需要进行扩展和替换,继续下一步。
  • 执行命令:经过第二次的解析,最终会执行一个date命令,从而得到日期和时间。

所以,eval就是在对已处理了的命令将要执行前重新做解析和扩展/替换后再去执行。它相对于不加eval的命令行多做了一次解析和扩展/替换。这对于部分命令行在Bash做了扩展或替换后(还是个没有生命力的命令行)不能正常运行时,能起到打通任督二脉的作用!

Sunflower

Stay hungry stay foolish

Comments