是否允許 shell 忽略腳本中的 NUL 字節?
因為這就是他們中的一些人正在做的事情。
> echo echo Hallo, Baby! | iconv -f utf-8 -t utf-16le > /tmp/hallo > chmod 755 /tmp/hallo > dash /tmp/hallo Hallo, Baby! > bash /tmp/hallo /tmp/hallo: /tmp/hallo: cannot execute binary file > (echo '#'; echo echo Hallo, Baby! | iconv -f utf-8 -t utf-16le) > /tmp/hallo > bash /tmp/hallo Hallo, Baby! > mksh /tmp/hallo Hallo, Baby! > cat -v /tmp/hallo # e^@c^@h^@o^@ ^@H^@a^@l^@l^@o^@,^@ ^@B^@a^@b^@y^@!^@ ^@
這是標準實際要求的一些兼容性問題嗎?因為它看起來非常危險和意外。
根據POSIX,
輸入文件應為文本文件,但行長不受限制¹
輸入中的 NUL 字元²使其成為 non-text,因此就 POSIX 而言,行為是未指定的,因此
sh
實現可以做任何他們想做的事情(並且符合 POSIX 的腳本不得包含 NUL)。有些 shell 會掃描前幾個字節的 0 並拒絕執行腳本,假設您錯誤地嘗試執行非腳本文件。
這很有用,因為如果系統在 上返回 ENOEXEC,則需要
exec*p()
函式、env
命令、sh
…find -exec
來呼叫 shell 來解釋命令,因此,如果您嘗試為錯誤的架構執行命令,最好獲得勝利’不要從你的 shell 執行一個二進製文件錯誤,而不是 shell 試圖將它理解為一個 shell 腳本。execve()
這是 POSIX 允許的:
如果執行檔不是文本文件,shell 可能會繞過此命令執行。
在標準的下一次修訂中將更改為:
shell 可以應用啟發式檢查來確定要執行的文件是否可以是腳本,並且如果它確定文件不能是腳本,則可以繞過此命令執行。在這種情況下,它應寫入錯誤消息,並應返回退出狀態 126。
注意:拒絕不能是腳本的文件的常見啟發式方法是在固定長度內的 <newline> 字節之前定位 NUL 字節文件的前綴。由於 sh 需要接受具有無限行長度的輸入文件,因此啟發式檢查不能基於行長度。
這種行為可能會妨礙 shell 自解壓存檔,儘管其中包含一個 shell 標頭,後跟二進制數據¹。
Shell 在其
zsh
輸入中支持 NUL,但請注意 NUL 不能在 的參數中傳遞execve()
,因此您只能在參數或內置命令或函式的名稱中使用它:$ printf '\0() echo zero; \0\necho \0\n' | zsh | hd 00000000 7a 65 72 6f 0a 00 0a |zero...| 00000007
(這裡定義和呼叫一個以 NUL 為名稱的函式,並將 NUL 字元作為參數傳遞給內置
echo
命令)。有些人會剝離它們,這也是明智的做法。
NUL
s 有時用作填充。例如,它們會被終端忽略(有時它們會被發送到終端以讓它們有時間處理複雜的控制序列(如輸入(字面意思))。文件中的空洞似乎被 NUL 填充,等等。請注意,非文本不限於 NUL 字節。它也是在語言環境中不形成有效字元的字節序列。例如,0xc1 字節值不能出現在 UTF-8 編碼文本中。因此,在使用 UTF-8 作為字元編碼的語言環境中,包含此類字節的文件不是有效的文本文件,因此也不是有效的
sh
腳本³。實際上,
yash
它是我所知道的唯一一個會抱怨這種無效輸入的 shell。¹ 在標準的下一次修訂中,它將更改為
輸入文件可以是任何類型,但要根據 shell 語法(XSH 2.10.2 Shell 語法規則的 XREF)解析的文件的初始部分應由字元組成,並且不應包含 NUL 字元。外殼不應強制執行任何行長度限制。
明確要求 shell 支持以沒有 NUL 字節的語法有效部分開頭的輸入,即使其餘部分包含 NUL,以解釋自解壓檔案。
² 和字元旨在根據語言環境的字元編碼進行解碼(參見 的輸出
locale charmap
),在 POSIX 系統上,NUL 字元(其編碼始終為字節 0)是唯一編碼包含字節 0 的字元。在其他換句話說,UTF-16 不屬於可以在 POSIX 語言環境中使用的字元編碼。³ 然而,腳本中存在語言環境更改的問題(例如分配
LANG
/LC_CTYPE
/LC_ALL
/LOCPATH
變數時),以及何時更改對解釋輸入的 shell 生效。