web-dev-qa-db-ja.com

Bashのファイル読み取りコマンド置換を理解する

私はBashが次の行をどのように扱うかを理解しようとしています:

$(< "$FILE")

Bashのmanページによると、これは次と同等です。

$(cat "$FILE")

この2行目の推論の行を追うことができます。 Bashは$FILEで変数展開を実行し、コマンド置換を入力し、$FILEの値をcatに渡し、catは$FILEの内容を標準出力に出力し、コマンド置換は次のように終了します。行全体を内部のコマンドの結果である標準出力に置き換えると、Bashは単純なコマンドのようにそれを実行しようとします。

ただし、上記の最初の行については、Bashが$FILEで変数置換を実行し、Bashが標準入力で読み取るために$FILEを開くどういうわけか標準入力が標準出力にコピーされます、コマンド置換が終了し、Bashは結果の標準出力を実行しようとします。

誰かが$FILEの内容がstdinからstdoutにどのように変わるか説明してくれませんか?

11
Stanley Yu

$(<file)`<file`でも機能)は、zshおよびbashによってコピーされたKornシェルの特殊な演算子です。コマンド置換によく似ていますが、実際はそうではありません。

POSIXシェルでは、簡単なコマンドは次のとおりです。

< file var1=value1 > file2 cmd 2> file3 args 3> file4

すべての部分はオプションであり、リダイレクトのみ、コマンドのみ、割り当てのみ、または組み合わせが可能です。

リダイレクトはあるがコマンドがない場合、リダイレクトは実行されます(つまり、> fileが開き、fileが切り捨てられます)が、何も起こりません。そう

< file

fileを読み取り用に開きますが、コマンドがないため何も起こりません。したがって、fileは閉じられ、それだけです。 $(< file)が単純なコマンド置換の場合、何も展開されません。

POSIX仕様$(script)では、scriptがリダイレクトのみで構成されている場合、は不特定の結果を生成します。これは、Korn Shellの特別な動作を可能にするためです。

Ksh(ここではksh93u+でテスト)で、スクリプトが1つだけで構成されている場合単純なコマンド(コメントは前後に許可されます)のみで構成されますリダイレクト(コマンドなし、割り当てなし)の場合、および最初のリダイレクトがstdin(fd 0)入力のみ(<<<または<<<)リダイレクトの場合、次のようになります。

  • $(< file)
  • $(0< file)
  • $(<&3)(実際には同じ演算子なので、実際には$(0>&3)も)
  • $(< file > foo 2> $(whatever))

だがしかし:

  • $(> foo < file)
  • また、$(0<> file)
  • また、$(< file; sleep 1)
  • また、$(< file; < file2)

その後

  • 最初のリダイレクト以外はすべて無視されます(それらは解析されます)
  • そして、それはfile/heredoc/herestring(または<&3のようなものを使用する場合はファイル記述子から読み取ることができるもの)の内容から末尾の改行文字を除いたものに展開されます。

$(cat < file)を使用しているかのように

  • 読み取りはcatではなく、シェルによって内部的に行われます
  • パイプも余分なプロセスも含まれていません
  • 上記の結果として、内部のコードはサブシェルで実行されないため、その後変更は残ります($(<${file=foo.txt})または$(<file$((++n)))のように)
  • 読み取りエラー(ファイルを開いているときやファイル記述子を複製しているときのエラーではありません)は警告なしで無視されます。

zshでも同じですが、ファイルの入力リダイレクトが1つしかない場合にのみ特別な動作がトリガーされます(<fileまたは0< file<&3<<<here< a < b...)

ただし、他のシェルをエミュレートする場合を除いて、次の場所にあります。

< file
<&3
<<< here...

つまり、コマンド置換以外のコマンドなしの入力リダイレクトのみがある場合、zsh$READNULLCMD(デフォルトではページャー)を実行し、入力リダイレクトと出力リダイレクトの両方がある場合は、$NULLCMDcat byしたがって、$(<&3)がその特殊な演算子として認識されない場合でも、ページャーを呼び出してそれを実行することで、kshのように機能します(そのページャーは、catのように動作します。stdoutがパイプ)。

ただし、ksh$(< a < b)aのコンテンツに展開されますが、zshではaおよびb(またはbmultiosオプションが無効になっている場合)、$(< a > b)abにコピーし、何も展開しない、などです。

bashにも同様の演算子がありますが、いくつかの違いがあります。

  • コメントは前では許可されますが、後では許可されません:

    echo "$(
       # getting the content of file
       < file)"
    

    動作しますが:

    echo "$(< file
       # getting the content of file
    )"
    

    何にも拡張されません。

  • zshと同様に、$READNULLCMDへのフォールバックはありませんが、1つのファイルstdinリダイレクトのみなので、$(<&3)$(< a < b)はリダイレクトを実行しますが、何も展開しません。

  • 何らかの理由で、bashcatを呼び出さない一方で、パイプを介してファイルのコンテンツをフィードするプロセスをフォークし、他のシェルよりも最適化がはるかに少なくなります。これは、実際には$(cat < file)のようなもので、catは組み込みcatになります。
  • 上記の結果として、その後行われた変更はすべて失われます(たとえば、上記の$(<${file=foo.txt})では、その$file割り当ては後で失われます)。

bashでは、IFS= read -rd '' var < filezshでも機能)は、textファイルの内容を読み込むより効果的な方法です変数。また、末尾の改行文字を保持するという利点もあります。 zsh$mapfile[file]も参照してください(zsh/mapfileモジュール内で、通常のファイルのみ)。これもバイナリファイルで機能します。

kshのpdkshベースのバリアントには、ksh93と比較していくつかのバリエーションがあることに注意してください。興味深いことに、mksh(これらのpdksh派生シェルの1つ)では、

var=$(<<'EOF'
That's multi-line
test with *all* sorts of "special"
characters
EOF
)

ヒアドキュメントのコンテンツ(末尾の文字なし)が一時ファイルやパイプを使用せずに展開されるという点で最適化されています。そうでない場合はヒアドキュメントの場合と同様に、効果的な複数行の引用構文になります。

kshzsh、およびbashのすべてのバージョンに移植できるようにするために、コメントを避け、内部で行われた変数への変更が保持される場合と保持されない場合があることを念頭に置いて、$(<file)のみに制限することをお勧めします。 。

14

bashは内部で行うため、$(cat < filename)を実行する場合と同様に、ファイル名を拡張してファイルを標準出力に出力します。これはbashの機能です。おそらく、bashソースコードを調べて、その機能を正確に知る必要があります。

ここで、この機能を処理する関数(bashソースコード、ファイル_builtins/evalstring.c_から):

_/* Handle a $( < file ) command substitution.  This expands the filename,
   returning errors as appropriate, then just cats the file to the standard
   output. */
static int
cat_file (r)
     REDIRECT *r;
{
  char *fn;
  int fd, rval;

  if (r->instruction != r_input_direction)
    return -1;

  /* Get the filename. */
  if (posixly_correct && !interactive_Shell)
    disallow_filename_globbing++;
  fn = redirection_expand (r->redirectee.filename);
  if (posixly_correct && !interactive_Shell)
    disallow_filename_globbing--;

  if (fn == 0)
    {
      redirection_error (r, AMBIGUOUS_REDIRECT);
      return -1;
    }

  fd = open(fn, O_RDONLY);
  if (fd < 0)
    {
      file_error (fn);
      free (fn);
      return -1;
    }

  rval = zcatfd (fd, 1, fn);

  free (fn);
  close (fd);

  return (rval);
}
_

$(<filename)$(cat filename)とまったく同じではないことに注意してください。ファイル名がダッシュ_-_で始まる場合、後者は失敗します。

$(<filename)は元々kshからのものであり、_Bash-2.02_からbashに追加されました。

8
cuonglm