除非特別說明,本文中出現的 Shell 均指 Bash 4.3。首先說一個基礎知識:Shell 中的變量在展開成值(Parameter Expansion)之后,這個值在某些上下文(Context)中,還會進行分詞操作(Word Splitting),但在另外一些上下文中,不會進行分詞操作。本文中把會進行分詞操作的上下文叫做列表上下文(List Context),把不會進行分詞的上下文叫做標量上下文(Scalar Context)。還有一個基礎知識再提一嘴,就是 Shell 在分詞時會跳過那些被雙引號包圍的詞。
因為 $* 和 $@ 這兩個特殊變量在以上兩種上下文中的展開結果不一樣,所以下面必須分兩種情況討論。
列表上下文
列表上下文是我們最熟悉的情況,比如在簡單命令的參數中,又比如在 for-in 語句的參數中,這些地方需要的都是多個詞,所以 Shell 規定在這些地方要進行分詞操作。
$*
$* 在列表上下文中會展開成 $1 $2 $3 ... 多個詞,而又因列表上下文存在分詞操作,所以 $1 $2 等等都會再被 IFS 分割。
$ set a "b c" d $ printf "%s\n" $* # $2 的值為 "b c",但由于 $2 本身沒有被雙引號包圍,所以會被分成兩個詞 b c,所以一共就成了 a b c d 四個參數 a b c d |
"$*"
"$*" 在列表上下文中會展開成 "$1c$2c$3...",c 是 IFS 的第一個字符,如果 IFS 為空,則 c 也是空,如果 IFS 不存在,則 c 為空格,雖然這里存在分詞操作,但由于展開后的值仍處于雙引號中,所以分詞操作不會有任何效果。
$ set a "b c" d $ IFS=: $ printf "%s\n" "$*" # 所有位置參數連接成了一個參數 a:b c:d |
$@
$@ 在列表上下文中的表現和 $* 在列表上下文中的表現完全一樣。
$ set a "b c" d $ printf "%s\n" $@ a b c d |
"$@"
"$@" 在列表上下文中會展開成 "$1" "$2" "$3" ...,由于展開后的每個值都處于雙引號中,所以分詞操作不會有任何效果。
$ set a "b c" d $ printf "%s\n" "$@" a b c d |
列表上下文是我們最熟悉的,Bash manual 對 $* 和 $@ 的講解也僅限于列表上下文中的表現,下面我們講講它們倆在標量上下文中的表現。
標量上下文
最常見的標量上下文就是賦值語句的右邊,此外還有 case 關鍵字的后面,以及 [[ ]] 之間等等,這些地方需要的都是一個詞,所以 Shell 規定在這些地方不進行分詞操作。
$*
$* 在標量上下文中展開成 $1c$2c$3...,c 是 IFS 的第一個字符,由于標量上下文沒有分詞操作,所以這就結束了,也就是說,$* 在標量上下文的效果等同于 "$*" 在列表上下文中的效果。
$ set a "b c" d $ IFS=: $ var=$* # var 的值成了 "a:b c:d" $ echo "$var" a:b c:d |
"$*"
"$*" 在標量上下文中展開成 "$1c$2c$3...",由于反正沒有分詞操作,所以和 $* 在標量上下文中的表現一樣。所以也就是說 var=$* 和 var="$*" 完全一樣。
$@
$@ 在標量上下文展開成 $1空格$2空格$3...,這里用“空格”字樣是為了說明展開后的值是一個詞。也就是說,$@ 和 $* 在標量上下文下的區別僅僅是前者用空格做分隔符后者用 IFS 的第一個字符做分隔符這一個區別。
$ set a "b c" d $ IFS=: $ var=$* # var 的值成了 "a b c d",不使用 IFS $ echo "$var" a b c d |
"$@"
"$@" 在標量上下文中展開成 "$1空格$2空格$3..." 和不加引號效果一樣,var=$@ 等效于 var="$@"。
再總結一下就是,在標量上下文中,$* 和 $@ 加不加引號都一樣,它倆的區別就是分隔符的區別,它倆展開后的結果都是用一個分隔符把所有位置參數連接成了一個詞。下面再用 [[ ]] 的代碼示例鞏固一下它倆的區別:
$ set a "b c" d $ IFS=: $ [[ "$*" == "a:b c:d" ]]; echo $? 0 $ [[ "$@" == "a b c d" ]]; echo $? 0 |
在實際編碼中沒必要記憶這些區別,你只需要記住一點,需要多個詞的時候用 "$@",需要一個詞的時候用 "$*",是的,永遠帶著引號。此外,由于 Posix 規范明確規定了“本規范不對 $@ 在標量上下文上的表現做任何定義”,所以上面的一些代碼示例在 Bash 以外的 Shell 上可能有不同的結果。
最后一句,$* 和 $@ 的所有表現都應該能推廣到帶 * 和 @ 下標的任意數組上。
文章列表