關於 Word Splitting 和 IFS 的三個細節

在 Bash manual 裏叫 Word Splitting,在 Posix 規範裏叫 Field Splitting,這二者指的是同一個東西,我把它翻譯成「分詞」,下面我就說三點不少人都忽略掉(或者說從沒仔細考慮過)的分詞細節。html

1. 分隔符究竟是字符仍是字符串?

IFS 裏面能夠包含多個字符,那麼在分詞的過程當中,是 IFS 中的每一個單獨的字符做爲分隔符,仍是由這些字符組合成的任意字符串做爲分隔符?咱們寫個簡單的例子證實一下:bash

$ var=a12b21c IFS=12spa

$ printf "<%s>\n" $var翻譯

<a>htm

<>文檔

<b>字符串

<>get

<c>it

因爲輸出了兩個空的字段,也就證實了是 1 和 2 兩個單獨的 IFS 字符做爲了分隔符,而不是 12 和 21 這兩個由 IFS 字符組成的字符串做爲了分割符。但結論沒這麼簡單,再看一個例子:io

$ var=$'1 \t 2' IFS=$' \t' #紅色背景的是空格

$ printf "<%s>\n" $var

<1>

<2>

在這個例子中,IFS 包含兩個字符:空格符和製表符, 若是說它們倆是單獨做爲分隔符的,那麼 $var 就應該被分割成四個字段,分別是 <1> <> <> <2>,但實際的結果並非這樣的。這是由於:空格符、製表符(\t)、換行符(\n)這三個空白符在 IFS 中會被特殊對待,Shell 會把它們按照任意順序任意數量組合成的字符串做爲分隔符,而不是單個字符做爲分隔符。在這個例子中,是「 \t 」總體做爲了一個分隔符,把 1 和 2 分紅了兩個字段。下面再演示一下 IFS 爲換行符的狀況:

$ var=$'1\n\n\n2\n\n\n3' IFS=$'\n'

$ printf "<%s>\n" $var

<1>

<2>

<3>

這個例子中,三個連續的換行符做爲了分隔符,把 var 分紅了三個字段。

若是 IFS 既包含空白符,又包含非空白符,會怎麼樣?

看下面的例子,IFS 中既有空白符 \n 又有非空白符 2:

$ var=$'1\n\n2\n\n3' IFS=$'\n2'

$ printf "<%s>\n" $var

<1>

<3>

咦?有些同窗就想問了:上面不是說,Shell 會把以任意個 IFS 包含的空白符組成的字符串做爲分隔符,把單個 IFS 中包含的非空白符做爲分隔符嗎,那不就是有三個分隔符:「\n\n」、「2」、「\n\n」嗎?但從表現上來看,是「\n\n2\n\n」總體做爲了一個分隔符,這是怎麼回事?

下面咱們就再說個法則:「一個 IFS 中包含的非空白符會和它兩邊存在的由 IFS 中包含的空白符組成的字符串組合成一塊兒做爲分隔符」。在上面的例子中,就是 2 和它兩邊的 「\n\n」 5個字符組合起來做爲了一個分割符,因此產生了 1 和 3兩個字段。

2. 尾部的空字段會被丟棄

$ var=:1:2:3: IFS=:

$ printf "<%s>\n" $var

<>

<1>

<2>

<3>

四個分隔符,應該把 var 切割成 5 個字段,但從結果上看,尾部的空字段不見了?是的,再說一個法則:分詞以後,若是最後一個字段是空的,那麼這個字段會被丟棄掉。其實,一個包含空值的變量在分詞以後會被丟棄,也符合這條法則:

$ var=""

$ set -- $var

$ echo $#

0

上面的例子中,var 的值就是空,因此在分詞以後也是隻有一個空的字段,也是最後一個字段,符合尾部空字段被丟棄的法則,因此 set 命令只看到了 -- 這一個參數。 

3. 首尾的空白符序列會被丟棄掉

$ var=$'\n1:2\n' IFS=$'\n:'

$ printf "<%s>\n" $var

<1>

<2>

這個例子中,分割符應該有三個,分別是 \n、:、\n,它們會把 var 分割成四個字段 <> <1> <2> <>,尾部的字段是空的,被丟棄,就成了 <> <1> <2>。咦?WTF,爲何和真實的輸出不符!下面是最後一條法則:在正式分詞以前,變量兩邊的由 IFS 包含的空白符組合成的序列會被丟棄掉,而後才進行正式分詞。在上面的例子中,var 會先被切頭去尾,也就變成 「1:2」,才進行正式的分詞,也就最終被分紅了 1 和 2 兩個字段了。注意,首尾的空白符序列只包含由 IFS 中包含的空白符組成的序列,好比上面的例子改一下:

$ var=$'\n1:2\n' IFS=$'\t:'

$ printf "<%s>\n" $var

<

1>

<2

>

因爲 \n 沒有包含在 IFS 中,因此 var 首尾的 \n 也就不會被去掉。 關於這點,Bash 的文檔記載有 bug,我給提 bug 修復了。

最後說一句,本文中所舉的例子都是用 parameter expansion 來演示的,command substitution 和 arithmetic expansion 雖然沒有演示,但一樣適用。

相關文章
相關標籤/搜索