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


11

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

$(< "$FILE")

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

$(cat "$FILE")

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

ただし、上記の1行目は$FILE、Bashが$FILEで変数置換を実行し、Bashが標準入力を読み取るために開き、標準入力が標準出力コピーされ、コマンド置換が終了し、結果の標準をBashが実行しようとすることを理解しています。出力。

誰かが私に$FILEstdinからstdoutへのコンテンツの流れを説明してくれませんか

回答:


-3

これ<は、bashコマンド置換の直接的な側面ではありません。これは(パイプのような)リダイレクト演算子であり、一部のシェルではコマンドなしで使用できます(POSIXではこの動作は指定されていません)。

おそらくそれはより多くのスペースでより明確になるでしょう:

echo $( < $FILE )

これは実質的に *よりPOSIXセーフと同じです

echo $( cat $FILE )

...これも効果的です*

echo $( cat < $FILE )

その最後のバージョンから始めましょう。これはcat引数なしで実行されます。つまり、標準入力から読み取られます。 $FILEにより標準入力にリダイレクトされる<ためcat、その内容が標準出力に出力されます。次に、$(command)置換はcatの出力をの引数にプッシュしますecho

bash(ただし、POSIX標準で)は、使用することができます<コマンドなし。 bash(とzshkshはなくdash)はcat <、新しいサブプロセスを呼び出さずに、あたかもそれを解釈します。これはシェルにネイティブなので、文字通り外部コマンドを実行するよりも高速ですcat*これが私が「実質的に同じ」と言う理由です。


つまり、最後の段落で「これbashは次のように解釈されます」と言ったときcat filename、この動作はコマンド置換に固有のものであることを意味しますか?私が一人で走っ< filenameたとしても、bashはそれを逃がさないからです。何も出力せず、プロンプトに戻ります。
スタンレーユー

コマンドはまだ必要です。@cuonglmから私の元のテキストを変更cat < filenamecat filenameている私は反対して戻ることがあります。
Adam Katz

1
パイプはファイルの一種です。シェルオペレーター|は、2つのサブプロセス間に(または、一部のシェルでは、サブプロセスからシェルの標準入力に)パイプを作成します。シェルオペレーター$(…)は、サブプロセスからシェル自体(標準入力ではない)へのパイプを作成します。シェルオペレーター<はパイプを含まず、ファイルを開き、ファイル記述子を標準入力に移動するだけです。
Gilles「SO-邪悪になるのをやめる」2015

3
< fileとはcat < file異なります(zshのような場合を除く$READNULLCMD < file)。< file完全にPOSIXでありfile、読み取り用に開いただけで何もしません(そのためfileすぐに閉じます)。それはだ$(< file)`< file`の特別な演算子であることkshzsh及びbash(および動作は、POSIXに指定されていないままです)。詳細については、私の回答を参照してください。
ステファンChazelas

2
@StéphaneChazelasのコメントを別の観点から見ると、最初の概算で$(cmd1) $(cmd2)は、通常と同じになり$(cmd1; cmd2)ます。しかし、ケースを見てcmd2います< file。と言えば$(cmd1; < file)、ファイルは読み込まれませんが、で読み込まれます$(cmd1) $(< file)。したがって、それがのコマンドを使用した$(< file)通常のケースである$(command)と言うのは誤りです< file。   $(< …)ある特殊なケースコマンド置換のではなく、リダイレクトの通常の使用。
スコット

14

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

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

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

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

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

< file

file読み取り用に開きますが、コマンドがないため何も起こりません。だから、fileそれは閉じられ、それだけです。$(< file)単純なコマンド置換であれば、何にも拡張されません。

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

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それだけで一つのファイル入力のリダイレクトがありますときには、特別な行動が唯一のトリガされたことを除いて同じです(<fileまたは0< file、なし<&3<<<here< a < b...)

ただし、他のシェルをエミュレートする場合を除いて:

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

それは、コマンドなしでのみ入力リダイレクション、コマンド置換の外側がある場合には、zsh実行されます$READNULLCMD(デフォルトではページャ)を、両方の入力と出力のリダイレクトが、あるとき$NULLCMDcatデフォルトでは)、その場合でも、$(<&3)その特別なとして認識されません演算子はksh、ページャーを呼び出して実行する場合と同じように機能します(cat標準出力がパイプになるため、ページャーは同じように動作します)。

しかしながらkshのは、$(< a < b)内容に拡大するa中、zshそれはの内容に展開、aおよびb(または単にb場合はmultiosオプションが無効になっている)、$(< a > b)コピーするaにはb、何も、などに拡大します

bash 演算子は似ていますが、いくつかの違いがあります。

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

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

    動作しますが:

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

    何にも拡張されません。

  • 以下のようzshに何のフォールバックはありませんが、一つだけのファイルSTDINリダイレクト、$READNULLCMDので$(<&3)$(< a < b)リダイレクトを行うが、何にも拡大します。

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

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

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

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

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

およびのすべてのバージョンに移植できるようにするにはksh、コメントの回避のみに制限し、内部で行われた変数への変更が保持される場合と保持されない場合があることに留意してください。zshbash$(<file)


$(<)ファイル名の演算子は正しいですか?ある<$(<)リダイレクト演算子、またはない、独自のオペレータは、全体事業者の一部でなければなりませんか $(<)
Tim

@Tim、どのように呼び出すかは問題ではありません。$(<file)file同様の方法でのコンテンツに展開することを意図しています$(cat < file)。それがどのように行われるかは、答えごとに詳しく説明されているシェルごとに異なります。必要に応じて、コマンド置換のように見えるものが(構文的に)単一のstdinリダイレクトのように見えるものを(構文的に)含むときにトリガーされる特別な演算子であると言えますが、ここでも、シェルに応じて警告とバリエーションが表示されます。
ステファンChazelas

@StéphaneChazelas:いつものように魅力的です。これをブックマークしました。だから、n<&mn>&m同じことを行いますか?それは知りませんでしたが、それほど驚くことではないと思います。
スコット

@スコット、はい、どちらもしdup(m, n)ます。stdioとを使用してksh86のいくつかの証拠を見ることができますfdopen(fd, "r" or "w")。しかし、シェルでstdioを使用しても意味がありません。そのため、違いが出るような最新のシェルが見つかるとは思いません。一つの違いは、つまり>&nあるdup(n, 1)(略して1>&nいるが、)<&nであるdup(n, 0)(略して0<&n)。
ステファンChazelas

正しい。もちろん、引数が2つの形式のファイル記述子複製呼び出しは呼び出されdup2()ます。 dup()は引数を1つだけ取り、と同様にopen()、使用可能な最小のファイル記述子を使用します。(今日、私dup3()関数があることを学びました。)
スコット

8

なぜなら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れましたBash-2.02


1
cat filenamecatはオプションを受け入れるため、ファイル名がダッシュで始まる場合は失敗します。を使用すると、ほとんどの最新システムでこの問題を回避できますcat -- filename
Adam Katz

-1

コマンド置換は、通常どおりにコマンドを実行し、コマンドを実行しているポイントで出力をダンプすると考えることができます。

コマンドの出力は、別のコマンドの引数として使用したり、変数を設定したり、forループで引数リストを生成したりすることもできます。

foo=$(echo "bar")変数の値が設定されます$fooにしますbar。コマンドの出力echo bar

コマンド置換


1
OPがコマンド置換の基本を理解していることは、質問からかなり明らかだと思います。問題はの特殊なケースに関するもの$(< file)であり、一般的なケースに関するチュートリアルは必要ありません。それがのコマンド$(< file)を使用$(command)した通常のケースであると言っている場合は、アダムカッツが言っ< fileているのと同じことを言っていることになります。
スコット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.