web-dev-qa-db-ja.com

Shellが$(<file)の出力の一部をコマンドとして扱うのはなぜですか?

読んでいるときにこの行を見ました IFSのブログ つまり:

_for i in $(<test.txt)
_

そして、$(<test.txt)がファイルの内容をSTDOUTに出力すると考えました。私はこれが間違っているかもしれませんが、好奇心からシェルでやろうとしました。そこで、ランダムなデータを持つarrayという名前のランダムなファイルを取得しました。

最初に私にこれを与えた_cat array_をしました:

_amit@C0deDaedalus:~/test$ 
amit@C0deDaedalus:~/test$ cat array
1)      Ottawa  Canada          345644
2)      Kabul   Afghanistan     667345
3)      Paris   France          214423
4)      Moscow  Russia          128793
5)      Delhi   India           142894
_

そして、私にこれを与えた$(<array)をしました:

_amit@C0deDaedalus:~/test$ $(<array)
1)      Ottawa  Ca: command not found
_

_<_が入力リダイレクトに使用されていることだけを知っていますが、ここでシェルによってコマンドとして解釈されているものを正確に取得していません。

シェルでのこの奇妙な出力の背後にある概念を誰かが説明できますか?

更新:-

_set -x_を実行すると、次のようになります。

_amit@C0deDaedalus:~/test$ $(<array)
+ '1)' Ottawa Canada 345644 '2)' Kabul Afghanistan 667345 '3)' Paris France 214423 '4)' Moscow Russia 128793 '5)' Delhi India 142894
+ '[' -x /usr/lib/command-not-found ']'
+ /usr/lib/command-not-found -- '1)'
1): command not found
+ return 127
amit@C0deDaedalus:~/test$ 
_
3
C0deDaedalus

$(command)構文はサブシェル環境でcommandを実行し、それ自体をcommandの標準出力に置き換えます。そして、 Bash Manualが言うように$(< file)$(cat file)と同等の高速です(ただし、POSIX機能ではありません)。

したがって、$(<array)を実行すると、Bashはその置換を実行し、最初のフィールドをコマンドの名前として使用し、残りのフィールドをコマンドの引数として使用します。

$ $(<array)
1): command not found

1)コマンド/関数がないので、エラーメッセージが出力されます。

ただし、特定のシナリオでは、おそらくIFS変数を変更したために、別のエラーメッセージが表示されます。

$ IFS=n; $(<array)
1)      Ottawa  Ca: command not found

編集1

私の推測では、IFSが何らかの形で変更されたため、シェルは1) Ottawa Caではなく1)を実行しようとしました。結局のところ、あなたはIFS関連の記事を読んでいました。あなたのIFSが奇妙な値になってしまったとしても、私は驚かないでしょう。

IFS変数は、単語分割またはフィールド分割として知られているものを制御します。これは基本的に、拡張コンテキストでシェルによって(またはreadなどの他のコマンドによって)データがどのように解析されるかを定義します。

Bashマニュアルでこのトピックについて説明しています

3.5.7単語分割

シェルは、Word分割の二重引用符内で発生しなかったパラメーター展開、コマンド置換、および算術展開の結果をスキャンします。

シェルは$IFSの各文字を区切り文字として扱い、他の展開の結果をこれらの文字をフィールドターミネータとして使用して単語に分割します。 IFSが設定されていない場合、またはその値がデフォルトの<space><tab><newline>である場合、最初と最後の<space><tab>、および<newline>のシーケンス前の展開の結果の一部は無視され、先頭または末尾にないIFS文字のシーケンスは単語を区切るのに役立ちます。 IFSの値がデフォルト以外の場合、空白文字spacetab、およびnewlineのシーケンスはの最初と最後で無視されます。空白文字がIFSIFS空白文字)の値である限り、Word。 IFS空白以外のIFS内の文字は、隣接するIFS空白文字とともに、フィールドを区切ります。 IFS空白文字のシーケンスも区切り文字として扱われます。 IFSの値がnullの場合、Wordの分割は発生しません。

明示的なnull引数(""または'')は保持され、空の文字列としてコマンドに渡されます。値を持たないパラメーターの展開に起因する、引用符で囲まれていない暗黙のnull引数は削除されます。値のないパラメーターが二重引用符で囲まれている場合、null引数が生成され、保持されて空の文字列としてコマンドに渡されます。引用符で囲まれたnull引数が、展開がnull以外のWordの一部として表示される場合、null引数は削除されます。つまり、Wordの分割とnull引数の削除の後、Word -d''-dになります。

拡張が発生しない場合、分割は実行されないことに注意してください。

IFSとコマンド置換の使用法に関するいくつかの例を次に示します。

例1:

$ IFS=$' \t\n'; var='hello     world'; printf '[%s]\n' ${var}
[hello]
[world]

$ IFS=$' \t\n'; var='hello     world'; printf '[%s]\n' "${var}"
[hello     world]

どちらの場合も、IFS<space><tab><newline>(デフォルト値)、varhello world、そしてprintfステートメントがあります。ただし、前者の場合は単語分割が実行されますが、後者の場合は実行されないことに注意してください(二重引用符はその動作を禁止するため)。 単語の分割は、引用符で囲まれていない展開で発生します。

例2:

$ IFS='x'; var='fooxbar'; printf '[%s]\n' ${var}
[foo]
[bar]

$ IFS='2'; (exit 123); printf '[%s]\n' ${?}
[1]
[3]

${var}${?}も空白文字を含まないため、このような場合は単語の分割は問題にならないだろうと思われるかもしれません。しかし、IFSが悪用される可能性があるため、これは正しくありません。 IFSは事実上すべての値を保持でき、悪用されやすいです。

例3:

$ $(echo uname)
Linux

$ $(xxd -p -r <<< 64617465202d75)
Sat Apr 28 12:46:49 UTC 2018

$ var='echo foo; echo bar'; eval "$(echo "${var}")"
foo
bar

これはWordの分割とは何の関係もありませんが、いくつかの汚いトリックを使用してコードを挿入する方法に注意してください。

関連する質問:

17
nxnev