「echo 123 >(cat)」の出力に「/dev/fd/63」が表示されるのはなぜですか?

「echo 123 >(cat)」の出力に「/dev/fd/63」が表示されるのはなぜですか?
$ echo 123 | cat 
123

予想通り、両方のコマンドが同じシェル内で実行されます。

>( ... )しかし、シェル内の 1 つのコマンドの出力をサブシェル内の 2 番目のコマンドの出力に接続する式を使用して接続すると、次のようになります。

$ echo 123 >(cat)
123 /dev/fd/63

これは他の値でも同様です。

$ echo 101 >(cat)
101 /dev/fd/63

$ echo $BASHPID >(cat)
3252 /dev/fd/63

command1 >(command2)は と同じだと思いましたcommand1 | command2が、 ではcommand1 >(command2)各コマンドが異なるシェル内にあるため、出力は同じになるはずです。どこが間違っているのでしょうか?

答え1

プロセス置換はファイル名に置き換えられます。このファイル名は、置換内>(thing)の標準入力に接続されているファイルに対応します。thing

次はその使用例のより良い例です。

$ sort -o >(cat -n >/tmp/out) ~/.profile

これにより、ファイルがソートされ~/.profile、出力が に送信されcat -n、行が列挙されて結果が に保存されます/tmp/out

したがって、質問に答えると、 はecho2 つの引数を取得し123/dev/fd/63は プロセス置換でプロセス/dev/fd/63の標準入力に接続されたファイルであるため、その出力が得られます。cat

サンプルコードを少し変更します。

$ echo 101 > >(cat)

これにより、標準出力に が生成されます101( の出力は のecho入力として機能するファイルにリダイレクトされcatcatそのファイルの内容が標準出力に生成されます)。


また、cmd1 | cmd2パイプラインでは、cmd2が と同じシェルで実行されない場合があることに注意してくださいcmd1(使用しているシェルの実装によって異なります)。 は、 ksh93説明したとおりに動作します (同じシェル)。一方、bashは のサブシェルを作成しますcmd2(lastpipeシェル オプションが設定されておらず、ジョブ制御がアクティブでない場合を除く)。

答え2

完全性のために

cmd1 >(cmd2)

ほとんど同じです

cmd1 | cmd2

殻の中にyash、そしてその殻だけの中に。

そのシェルでは、>(cmd)プロセスはリダイレクションプロセスである/ />(cmd)とは対照的にkshbashzsh代替

ではを待たないのでcmd1 >(cmd2)、厳密には同等ではありません。そのため、次のようになります。yashcmd2

$ yash -c 'echo A >(cat); echo B'
B
A
$ yash -c 'echo A | cat; echo B'
A
B

対照的に、プロセス代替ファイル パス (通常は名前付きパイプまたは、事前に作成されたパイプへの fd) に展開され、書き込み用に開くと、出力を に送信できるようになり/dev/fd/<x>ます。<x>cmd

プロセス置換は によって導入されましたがksh、そのシェルでは、それらをリダイレクトの引数として渡すことはできません。

ksh -c 'cmd1 > >(cmd2)'

プロセスリダイレクトをエミュレートすることyashはできません。ここでは、置換の結果得られたファイル名を、次のようにコマンドの引数として渡す必要があります。

ksh -c 'diff <(echo a) <(echo b)'

bashおよびで動作しますzsh

ただし、のプロセスリダイレクトbashの場合と同様にyash、シェルはコマンド ( cmd2) を待機しません。つまり、

$ bash -c 'echo A > >(cat); echo B'
B
A

kshプロセス置換は次のようにエミュレートできますyash:

cmd1 /dev/fd/5 5>(cmd2)   

のように:

diff /dev/fd/3 3<(echo a) /dev/fd/4 4<(echo b)

答え3

なぜならそれがプロセス置換の目的です、置換内のコマンドがファイル名として表示されます。内部的には、パイプを介してコマンドを接続し、/dev/fd/NNメインコマンドへのパスを指定して、既に開いているファイル記述子をパイプに開くことができます。

これはパイプとは異なります。パイプはファイル名のようなものを一切含まずに接続しますstdoutstdinプロセス置換は、1 つのコマンド ラインで複数の置換を行うことができる点でより柔軟ですが、メイン コマンドでファイルを名前 (ではcatなく、例echo) で開く必要があります。

次のようにして、プロセス置換でパイプをエミュレートできます。

echo foo > >(cat -n)

答え4

$ echo 1 >(cat > /dev/null)
1 /dev/fd/63
$ echo echo >(cat /dev/null)
echo /dev/fd/63

# We can trace how the commands are executed
# so long as we avoid using shell builtin commands,
# and run the equivalent external program instead, i.e. /usr/bin/echo

$ strace -f -e execve bash -c '/usr/bin/echo >(cat /dev/null)'
execve("/usr/bin/bash", ["bash", "-c", "/usr/bin/echo >(cat /dev/null)"], [/* 56 vars */]) = 0
strace: Process 4213 attached
[pid  4212] execve("/usr/bin/echo", ["/usr/bin/echo", "/dev/fd/63"], [/* 56 vars */]) = 0
strace: Process 4214 attached
[pid  4214] execve("/usr/bin/cat", ["cat", "/dev/null"], [/* 56 vars */]/dev/fd/63
) = 0
[pid  4212] +++ exited with 0 +++
[pid  4214] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=4214, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

# Apparently, the order of evaluation is arranged so this works nicely:

$ echo 1 > >(cat > /dev/null)
$

関連情報