我們知道,在 Bash 中,當變數出現在一個賦值語句的右側時,變數只會展開,不會分詞,即便變數兩邊沒有引號:
$ foo="1 2" $ bar=$foo # 不會被拆分成 bar=1 和 2 兩個詞 $ echo "$bar" 1 2 |
但是,當一個形似賦值語句的詞,作為引數傳給一個命令時,這時分詞步驟就不會少了:
$ foo="1 2" $ printf '%s\n' 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' |
果然,被當成普通的引數了。