我们知道,在 Bash 中,当变量出现在一个赋值语句的右侧时,变量只会展开,不会分词,即便变量两边没有引号:
$ foo="1 2" $ bar=$foo # 不会被拆分成 bar=1 和 2 两个词 $ echo "$bar" 1 2 |
但是,当一个形似赋值语句的词,作为参数传给一个命令时,这时分词步骤就不会少了:
$ foo="1 2" $ printf '%s ' bar=$foo bar=1 2 |
bar=$foo 虽然看起来像是赋值语句,但因为它出现在了命令名称的后面,所以它就是个普通的参数。它会先展开成 bar=1 2,然后被拆分成 bar=1 和 2 两个参数传给 printf。外部命令也一样:
$ valueOfa="1 b=2" $ env -i a=$valueOfa env # 你以为传给 env 的第二个参数是 "a=1 b=2",但其实是 "a=1" 和 "b=2" 两个参数 a=1 b=2 |
可是,有一些命令却是特殊的,比如 alias:
$ valueOfa="1 b=2" $ alias a=$valueOfa # 传给 alias 命令的会是 "a=1" 和 "b=2" 两个参数? $ alias alias a='1 b=2' # 并不是,valueOfa 这个变量居然没有被分词! |
像 alias 这样的内部命令一共有 6 个,分别是:alias、declare、typeset、export、readonly、local,它们的功能都是声明一个什么东西,变量或者别名。Bash 对它们的参数做了特殊对待,如果满足赋值语句的格式,则不对其中的变量进行分词。当然你也可以就把它们看成是赋值语句,反正赋值语句的效果是创建一个变量,这些参数的效果也是创建一个变量(除了 alias 命令)。Bash 手册也直接把它们说成是赋值语句:
Assignment statements may also appear as arguments to the
alias
,declare
,typeset
,export
,readonly
, andlocal
builtin commands.
在 Bash 的实现里,是专门用一个函数对这样的参数做了特殊处理:
/* This is a hack to suppress word splitting for assignment statements given as arguments to builtins with the ASSIGNMENT_BUILTIN flag set. */ static void fix_assignment_words (words) WORD_LIST *words; {
...
等等,还没完。上面说,如果满足赋值语句的格式,这些参数才会被特殊对待,那如果不满足呢?
我们知道,赋值语句的左边必须是合法的标识符,合法的标识符得符合“字母下划线开头后跟若干个字母数字下划线”。这样的话 1=$foo 就必定不是个合法的赋值语句了,下面试一把:
$ foo="1 2=2" $ alias 1=$foo $ alias alias 1='1' alias 2='2' |
果然,被当成普通的参数了。