Bash

如何在 Bash 中使用空字節?

  • January 15, 2018

我已經讀過,因為 Bash 中的文件路徑可以包含除空字節(零值字節,$'\0')之外的任何字元,因此最好使用空字節作為分隔符。例如,如果 的輸出find將被發送到另一個程序,建議使用該-print0選項(對於find具有它的版本)。

但是,儘管這樣的事情可以正常工作(列印由換行符分隔的文件路徑——別擔心,這只是一個展示,我實際上並沒有在真正的腳本中這樣做):

find -print0 \
 | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

像這樣的東西不起作用

for file in * ; do echo -n "$file"$'\0' ; done \
 | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

當我只嘗試for-loop 部分時,我發現它只是將所有文件名一起列印,中間沒有空字節。

為什麼是這樣?這是怎麼回事?

Bash 在內部使用 C 風格的字元串,這些字元串以空字節終止。這意味著 Bash 字元串(例如變數的值或命令的參數)實際上永遠不會包含空字節。例如,這個小腳本:

foobar=$'foo\0bar'    # foobar='foo' + null byte + 'bar'
echo "${#foobar}"     # print length of $foobar

實際列印3,因為$foobar實際上只是'foo':bar出現在字元串末尾之後。

同樣,echo $'foo\0bar'只列印foo,因為echo不知道該\0bar部分。

如您所見,該\0序列實際上在$'...'-style 字元串中非常具有誤導性;它看起來像字元串中的一個空字節,但它最終不會以這種方式工作。在您的第一個範例中,您的read命令具有-d $'\0'. 這行得通,但只是因為-d ''也行得通!(這不是 的明確記錄的功能read,但我認為它的工作原理相同:''是空字元串,因此它的終止空字節立即出現。記錄為使用“ delim的第一個字元”,我猜它甚至可以工作如果“第一個字元”超過了字元串的結尾!)-d *delim*

但是正如您從find範例中知道那樣,命令可以列印出空字節,並將該字節通過管道傳輸到另一個將其作為輸入讀取的命令。其中沒有任何部分依賴於在 Bash 中的字元串中儲存空字節。您的第二個範例的唯一問題是我們不能$'\0'在命令的參數中使用;echo "$file"$'\0'可以愉快地在最後列印空字節,只要它知道你想要它。

echo因此,您可以使用代替 using ,它支持與-style 字元串printf相同類型的轉義序列。$'...'這樣,您可以列印一個空字節,而不必在字元串中包含一個空字節。看起來像這樣:

for file in * ; do printf '%s\0' "$file" ; done \
 | while IFS= read -r -d '' ; do echo "$REPLY" ; done

或者簡單地說:

printf '%s\0' * \
 | while IFS= read -r -d '' ; do echo "$REPLY" ; done

(注意:echo實際上還有一個-e標誌可以讓它處理\0並列印一個空字節;但它也會嘗試處理文件名中的任何特殊序列。所以這種printf方法更健壯。)


順便說一句,有些 shell確實允許字元串中包含空字節。例如,您的範例在 Zsh 中執行良好(假設預設設置)。然而,不管你的 shell 是什麼,類 Unix 作業系統都沒有提供在程序參數中包含空字節的方法(因為程序參數作為 C 風格的字元串傳遞),所以總會有一些限制。(您的範例只能在 Zsh 中工作,因為echo它是內置的 shell,因此 Zsh 可以在不依賴作業系統支持來呼叫其他程序的情況下呼叫它。如果您使用command echo而不是echo,那麼它會繞過內置echo程序並在$PATH,你會在 Zsh 中看到與在 Bash 中相同的行為。)

引用自:https://unix.stackexchange.com/questions/174016