forループでバックティックを使用しない理由

forループでバックティックを使用しない理由

しばらく前にスクリプトに関する質問に対する回答を投稿しました。誰かが次のコマンドを使用してはいけないと指摘しました。

for x in $(cat file); do something; done 

しかし、代わりに:

while read f; do something; done < file

猫の無駄な使用記事では問題全体を説明したかったのですが、唯一の説明は次のとおりです。

バックティックの結果がシェルが許容できるコマンドラインの長さ以下であることを知らない限り、バックティックは非常に危険です。 (実際にはこれはカーネル制限です。limits.hの定数ARG_MAXはシステムがどのくらいかかるかを示します。POSIXでは、ARG_MAXが少なくとも4,096バイトになるように要求します。

これを正しく理解したら、コマンドで非常に大きなファイルの出力を使用すると、bash(?)がクラッシュする必要があります(limits.hファイルで定義されているARG_MAXを超える必要があります)。そこで、次のコマンドでARG_MAXを確認しました。

> grep ARG_MAX /usr/src/kernels/$(uname -r)/include/uapi/linux/limits.h
#define ARG_MAX       131072    /* # bytes of args + environ for exec() */

その後、スペースなしでテキストを含むファイルを作成しました。

> ls -l
-rw-r--r--. 1 root root 100000000 Aug 21 15:37 in_file

次に、次を実行します。

for i in $(cat in_file); do echo $i; done

ああ、ひどいことはありませんでした。

それでは、「ループのある猫を使用しないでください」全体が危険であるかどうかを確認するにはどうすればよいですか?

ベストアンサー1

file含める項目によって異なります。たとえば、IFSで区切られたシェルグローバルリストを含める場合(デフォルトはと仮定$IFS):

/var/log/*.log /var/adm/*~
/some/dir/*.txt

さて、これがfor i in $(cat file)行く方法です。引用解除が行うことは次のとおりです。$(cat file)末尾の改行文字を削除した状態で、出力に分割+グローブ演算子を適用します。cat fileしたがって、これらのglobの拡張によって引き起こされる各ファイル名を繰り返します(globがどのファイルとも一致しない場合、この場合、globはそのまま残りますが拡張されません)。

分離された各行を繰り返すには、file次のようにします。

while IFS= read -r line <&3; do
{
  something with "$line"
} 3<&-
done 3< file

ループを使用すると、for空白でない各行を繰り返すことができます。

IFS = '
'#改行文字のみに分割します(実際には一連の改行文字と
  #改行は次のようになるため、先行および末尾の文字を無視します。
  #IFS スペース文字)
set -o noglob# 無効全体的な状況分割+グローブ演算子の一部:
$(catファイル)の行に対して、次のようにします。
   「$line」があるもの
完璧

しかし:

while read line; do
  something with "$line"
done < file

それは言葉ではありません。それはfile非常に複雑な方法でコンテンツを読むここ$IFSで、バックスラッシュ文字は特別に扱われます。

とにかく、引用符付きテキストが参照するARG_MAX制限は、execve()システムコール(引数と環境変数の累積サイズに関連)にあるため、適用できるファイルシステムを使用してコマンド実行が実行された場合にのみ適用されます。コマンド置換分割+グローブ演算子の非常に長い拡張です(テキストが複数のアカウントで誤解を招く可能性があり、間違っています)。

たとえば、次のように動作します。

cat -- $(cat file) # with shell implementations where cat is not builtin

しかし、以下ではそうではありません。

for i in $(cat file)

execve()システムコールは含まれません。

比較する:

bash-4.4$ echo '/*/*/*/*' > file
bash-4.4$ true $(cat file)
bash-4.4$ n=0; for f in $(cat file); do ((n++)); done; echo "$n"
523696
bash-4.4$ /bin/true $(cat file)
bash: /bin/true: Argument list too long

bash使用されているtrue組み込みコマンドまたはループは機能しますforが、実行は機能しません/bin/true。サイズはわずか9バイトですが、シェルはグローブを拡張するため、数メガバイトfileだけ拡張されます$(cat file)/*/*/*/*

追加資料:

おすすめ記事