web-dev-qa-db-ja.com

「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)では、各コマンドは異なるシェル内にあるため、同じ出力が得られます。どこが間違っているのですか?

5
sharkant

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

以下は、その使用のより良い例です。

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

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

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

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

$ echo 101 > >(cat)

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


また、cmd1 | cmd2パイプラインでは、cmd2cmd1と同じシェルで実行されていない場合があります(使用しているシェルの実装によって異なります)。 ksh93は記述したとおりに機能し(同じシェル)、bashcmd2のサブシェルを作成します(そのlastpipeシェルオプションが設定されており、ジョブ制御がアクティブでない場合) )。

10
Kusalananda

完全を期すために

cmd1 >(cmd2)

ほとんど同じです

cmd1 | cmd2

yashシェル、およびそのシェルのみ。

そのシェルでは、>(cmd)はプロセスリダイレクションであり、ksh/bash/zsh>(cmd)はプロセスsubstitutionです。

cmd1 >(cmd2)では、yashcmd2を待機しないため、厳密には同等ではありません。

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

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

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

ksh -c 'cmd1 > >(cmd2)'

yashをエミュレートするプロセスのリダイレクトは機能しません。そこでは、次のようなコマンドの引数として、置換の結果として得られたファイル名を渡すことを意図しています。

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

bashzshで動作します。

ただし、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)
6

それがプロセス置換の機能です であるため、置換内のコマンドはファイル名として表示されます。内部的には、パイプを介してコマンドを接続し、/dev/fd/NNメインコマンドへのパス。これにより、すでに開いているファイル記述子をパイプに開くことができます。

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

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

echo foo > >(cat -n)
5
ilkkachu
$ 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)
$
3
sourcejedi