シェルはスクリプト内の NUL バイトを無視できますか?

シェルはスクリプト内の 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^@!^@
^@

これは実際には互換性の問題なのでしょうか必須標準的にはどうでしょうか?かなり危険で予想外に見えるからです。

答え1

に従ってPOSIX

入力ファイルはテキストファイルで、行の長さは無制限とする。¹

入力内のNUL文字²テキスト以外のものにするPOSIXに関する限り動作は未定義であり、sh実装は好きなようにできる(POSIX準拠の脚本NUL を含めることはできません。

最初の数バイトをスキャンして 0 を探し、誤ってスクリプト以外のファイルを実行しようとしたと想定してスクリプトの実行を拒否するシェルがいくつかあります。

これは、exec*p()関数、envコマンドshfind -exec...が必須システムがENOEXECで戻った場合、シェルを呼び出してコマンドを解釈することになりますexecve()。そのため、間違ったアーキテクチャのコマンドを実行しようとする場合は、バイナリを実行しないシェルがそれをシェル スクリプトとして理解しようとするよりも、シェルからのファイル エラーの方が重要です。

これは POSIX で許可されています:

実行可能ファイルがテキスト ファイルでない場合、シェルはこのコマンドの実行をバイパスすることがあります。

次回の規格改訂ではに変更されます:

シェルは、実行するファイルがスクリプトであるかどうかを判断するためにヒューリスティック チェックを適用し、ファイルがスクリプトではないと判断された場合はこのコマンドの実行をバイパスすることがあります。この場合、シェルはエラー メッセージを書き込み、終了ステータス 126 を返します
。注: スクリプトではないファイルを拒否するための一般的なヒューリスティックは、ファイルの固定長プレフィックス内で <newline> バイトの前に NUL バイトを配置することです。sh は行の長さに制限のない入力ファイルを受け入れる必要があるため、ヒューリスティック チェックは行の長さに基づいて行うことはできません。

ただし、この動作は、シェル ヘッダーとそれに続くバイナリ データ¹ を含むシェル自己解凍型アーカイブの妨げになる可能性があります。

シェル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 を使用して関数を定義して呼び出し、組み込みechoコマンドに引数として NUL 文字を渡します)。

それらを削除するものもありますが、これも賢明な方法です。NULはパディングとして使用されることがあります。たとえば、端末では無視されます (端末に複雑な制御シーケンス (キャリッジ リターン (文字通り) など) を処理する時間を与えるために、端末に送信されることもありました)。ファイル内の穴は、NUL などで埋められているように見えます。

非テキストは NUL バイトだけに限定されないことに注意してください。これは、ロケールで有効な文字を形成しないバイトのシーケンスでもあります。たとえば、0xc1 バイト値は、UTF-8 でエンコードされたテキストでは発生しません。したがって、文字エンコードとして UTF-8 を使用するロケールでは、このようなバイトを含むファイルは有効なテキスト ファイルではなく、したがって有効なshスクリプトではありません³。

実際には、yashこのような無効な入力についてエラーを発するシェルは、私が知る限りこれだけです。


¹ 次回の規格改訂では、それは変わるだろう

入力ファイルは任意のタイプにすることができますが、シェル文法 (XSH 2.10.2 シェル文法規則への XREF) に従って解析されるファイルの最初の部分は文字で構成され、NUL 文字を含んではなりません。シェルは行の長さの制限を強制しません。

自己解凍アーカイブを考慮して、残りの部分に NUL が含まれている場合でも、NUL バイトのない構文的に有効なセクションで始まる入力をシェルがサポートすることを明示的に要求します。

² および文字は、ロケールの文字エンコーディングに従ってデコードされることになっています ( の出力を参照locale charmap)。POSIX システムでは、エンコーディングにバイト 0 が含まれる文字は NUL 文字 (エンコーディングは常にバイト 0) のみです。つまり、UTF-16 は、POSIX ロケールで使用できる文字エンコーディングには含まれていません。

LANG³ ただし、スクリプト内でロケールが変更される ( ///変数が割り当てられるときなど)LC_CTYPEかどうか、また、どの時点で入力を解釈するシェルに変更が有効になるかという問題があります。LC_ALLLOCPATH

答え2

この動作の理由は少し複雑です...

まず、最近のシェルには、潜在的にバイナリ ファイル (ヌル バイトを含む) のチェック機能が含まれていますが、このチェックではファイルの最初の行のみが検証されます。これが、最初の行の '#' によって動作が変わる理由です。従来の Bourne Shell にはバイナリ チェック機能がなく、おっしゃったように動作するのに '#' も必要ありません。

次に、Bourne Shell がマルチバイト文字をサポートするために使用する特定のメソッドは、 null バイトに対して文字長 0 を返すmbtowc()ため、すべてのmbtowc()null バイトを単にスキップし、これによりループが発生して次の文字が再試行されます。

Bourne Shell は 1988 年頃にこの種のコードを導入し、他のシェルがその動作をコピーした可能性があります。

関連情報