それは彼らの何人かがしていることだからです。
> 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文字² それを非テキストにする なので、POSIXに関する限り、動作は指定されていないため、sh
実装は、必要なすべてを実行できます(POSIX準拠- scriptにNULを含めることはできません)。
最初の数バイトを0でスキャンし、スクリプト以外のファイルを誤って実行しようとしたと想定してスクリプトの実行を拒否するシェルがいくつかあります。
exec*p()
関数、env
コマンド、sh
、_find -exec
_...は必須であるため、これは便利です。システムがexecve()
でENOEXECを返した場合にコマンドを解釈するため、間違ったアーキテクチャーに対してコマンドを実行しようとする場合は、バイナリを実行しないを取得することをお勧めしますシェルよりもシェルからのファイルエラーが、シェルスクリプトとして理解しようとしています。
それは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を名前として関数を定義して呼び出し、NUL文字を引数として組み込みecho
コマンドに渡します)。
いくつかはそれらを取り除くでしょうが、これも賢明なことです。 NUL
sはパディングとして使用されることがあります。たとえば、ターミナルでは無視されます(それらは、キャリッジリターン(文字通り)などの複雑な制御シーケンスを処理する時間を与えるためにターミナルに送信されることがあります。)ファイルの穴は、NULなどで埋められているように見えます。
非テキストはNULバイトに限定されないことに注意してください。また、ロケールで有効な文字を形成しない一連のバイトです。たとえば、UTF-8エンコードされたテキストでは0xc1バイトの値は発生しません。したがって、UTF-8を文字エンコーディングとして使用するロケールでは、そのようなバイトを含むファイルは有効なテキストファイルではないため、有効なsh
スクリプト³ではありません。
実際には、yash
は、そのような無効な入力について文句を言う私が知っている唯一のシェルです。
the規格の次の改訂では、 変更される予定 から
入力ファイルはどのタイプでもかまいませんが、シェル文法(XREFからXSH 2.10.2シェル文法規則)に従って解析されることを意図したファイルの最初の部分は、文字で構成され、NUL文字を含んではなりません。シェルは行の長さの制限を強制しません。
自己解凍型アーカイブを考慮して、残りがNULを含んでいても、NULバイトのない構文的に有効なセクションで始まる入力をサポートすることをシェルに明示的に要求します。
²と文字は、ロケールの文字エンコード(_locale charmap
_の出力を参照)に従ってデコードされることを意図しており、POSIXシステムでは、NUL文字(エンコードは常にバイト0です)がエンコードに含まれる唯一の文字ですつまり、UTF-16は、POSIXロケールで使用できる文字エンコーディングには含まれていません。
howeverただし、スクリプト内でロケールが変化する問題(LANG
/_LC_CTYPE
_/_LC_ALL
_/LOCPATH
変数が割り当てられる場合など)とその時点での問題があります。変更は、シェルが入力を解釈するときに有効になります。
この動作の理由は少し複雑です...
まず、最近のシェルには、潜在的にバイナリファイル(nullバイトを含む)のチェックが含まれていますが、このチェックはファイルの最初の行のみを検証します。これが、最初の行の「#」が動作を変更する理由です。歴史的なBourne Shellにはそのバイナリチェックがなく、あなたが述べたように動作するために「#」も必要としません。
次に、Bourne Shellがmbtowc()
を介してマルチバイト文字をサポートするために使用する特定のメソッドは、mbtowc()
がnullバイトの文字長0を返し、これによりループが次のキャラクター。
Bourne Shellは1988年頃にこの種のコードを導入し、他のシェルがこの動作をコピーした可能性があります。