如何在 Bash 中使用空字節?
我已經讀過,因為 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 中相同的行為。)