Bashのファイル読み取りコマンドの置き換えについて

Bashのファイル読み取りコマンドの置き換えについて

Bashが次の行を正確に処理する方法を理解しようとしています。

$(< "$FILE")

Bashのマニュアルページによると、これは次のようになります。

$(cat "$FILE")

2行目から推論の流れに従うことができます。 Bashは入力コマンドの置換時に変数拡張を実行して$FILE値をに渡し、catはOrderの内容を出力します。$FILEcat$FILE

ただし、上記の最初の行の場合は、次のように解釈します。 Bashはで変数置換を実行し、Bashは標準入力から読み取るために$FILE開きます。$FILE何らかの方法で標準入力を標準出力にコピーします。、コマンド置換が完了し、Bashは生成された標準出力を実行しようとします。

$FILEコンテンツがstdinからstdoutにどのように移動するかを説明できますか?

答え1

$(<file)(ksh93でも、`<file`およびで使用されます)は、およびからコピーされた${<file;}Kornシェルの特殊演算子です。コマンドの置換と非常に似ているようですが、そうではありません。zshbash

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

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

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

リダイレクトがありますが、コマンドがなければリダイレクトは行われますが(したがってaが> file開かれ、切り捨てられますfile)、何も起こりません。だから

< file

読み取り用に開かれますfileが、コマンドがないため、何も起こりません。その後、file終了しました。$(< file)簡単なものならコマンドの置き換えすると、空の状態に展開されます。

内部にPOSIX仕様、inにリダイレクトのみが含まれている$(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))))はその後も保持されます。
  • 読み取りエラー(ファイルを開いたりファイルディスクリプタをコピーしたときに発生するエラーではありません)は自動的に無視されます。

では、ファイル入力リダイレクト(または、no、...)zshが1つしかない場合にのみ特殊動作がトリガされることを除いて同じです。<file0< file<&3<<<here< a < b

しかし、他のシェルをシミュレートすることに加えて、つまり、< file コマンドなしで入力リダイレクトが1つしかない場合は、コマンド置換の外部でzsh実行$READNULLCMD(デフォルトではポケットベル)で、リダイレクトが多いか< file<&3、、、、.. )(デフォルト)であるため、対応<<<textする特殊演算子として認識されなくても、呼び出して実行したように動作します。<a <b>file<a >b$NULLCMDcat$(<&3)kshcat

ただし、の内容kshは次に展開されますが、$(< a < b)inの内容は次の内容に展開されます。azsha そしてb(またはそのオプションが無効になっている間b)コピーされ、空の状態に展開されます。multios$(< a > b)ab

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

  • コメントは以前は許可されていますが、その後は許可されません。

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

    動作しますが:

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

    何も膨らませなかった。

  • のように、zshstdinのファイルリダイレクトは1つだけですが、aへのフォールバックはないため、リダイレクトは行われますが、$READNULLCMD拡張子は空です。$(<&3)$(< a < b)

  • 何らかの理由でbash呼び出していない場合でも、catプロセスを分岐してファイルの内容をパイプするため、他のシェルよりも最適化がはるかに少なくなります。実際に組み込まれたのと同じです$(cat < file)catcat

  • 上記の理由により、変更は失われます($(<${file=foo.txt})たとえば、上記の場合は割り当てが$file失われます)。

((適用))は、コンテンツを読むためのより効率的な方法ですbashIFS= read -rd '' var < filezshテキスト変数にファイルを置きます。また、末尾の改行文字を保存できるという利点もあります。また、($mapfile[file]モジュールでは、通常のファイルのみ)バイナリファイルでも機能します。zshzsh/mapfile

pdksh ベースのバリアントにはkshksh93 に比べていくつかの変更があります。興味深いことにmksh(pdksh派生シェルの1つ)から

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

ここにある文書のように、一時ファイルやパイプを使わずに、ここの文書の内容(末尾改行なし)が拡張され、有効な複数行参照構文になるため最適化されました。

kshzshおよびのすべてのバージョンへの移植性のために、コメントを避け、変数の変更が維持されても維持されない可能性があることを覚えておくのがbash最善です。$(<file)

答え2

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)もともとからkshbashに追加されましたBash-2.02

答え3

以下は、違いを示し、説明するbash 3.2スニペットです。

  • strace を使用してプロセスを追跡し、execve 呼び出しを表示します。strace -f -e trace=execve
  • bashコマンドを実行して文字列を読み取るか、またはbash -c読み込みます。/bin/cat
  • これに合わせてパラレルモードの出力を80列に分割します。diff -y -W 80

execve(/bin/cat...)違いの右側に追加の内容が表示されます。

$ echo $BASH_VERSION
3.2.25(1)-release
$ echo "hi" >/tmp/f
$ strace -f -e trace=execve /bin/bash -c 'echo $(</tmp/f)'          >/tmp/no_cat 2>&1
$ strace -f -e trace=execve /bin/bash -c 'echo $(/bin/cat </tmp/f)' >/tmp/wi_cat 2>&1
$ diff -y -W 80 /tmp/no_cat /tmp/wi_cat
execve("/bin/bash", ["/bin/bash", "-c | execve("/bin/bash", ["/bin/bash", "-c
Process 24253 attached (waiting for p | Process 24256 attached (waiting for p
Process 24253 resumed (parent 24252 r | Process 24256 resumed (parent 24255 r
Process 24253 detached                | Process 24257 attached (waiting for p
                                      > Process 24257 resumed (parent 24256 r
                                      > Process 24256 suspended
                                      > [pid 24257] execve("/bin/cat", ["/bin
                                      > Process 24256 resumed
                                      > Process 24257 detached
                                      > [pid 24256] --- SIGCHLD (Child exited
                                      > Process 24256 detached
--- SIGCHLD (Child exited) @ 0 (0) --   --- SIGCHLD (Child exited) @ 0 (0) --
hi                                      hi

答え4

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

スペースが多ければ、より明確になることもあります。

echo $( < $FILE )

これは効果的に*より多くのPOSIXセキュリティと同じ

echo $( cat $FILE )

...これも動作します*

echo $( cat < $FILE )

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

(POSIX標準ではbashありません)<コマンドなしで使用できます。 bash(およびzshおよびkshなしdash)はifとして解釈されますcat <が、新しい子プロセスを呼び出すことはありません。これはシェルにデフォルトで提供されているため、外部コマンドを直接実行するよりも高速ですcat*それで私は「事実上同じ」と言ったのです。

関連情報