これはzsh
およびで再現可能ですbash
。
さらに混乱するのは、echo | ( xargs; : ) > >(cat)
ハングアップしないことです。これは、およびでも再現可能zsh
ですbash
。
xargs
提供されているGNU を使用すると、brew install findutils
ハングしませんecho | gxargs > >(cat)
。
実際、私のシステム以外でこのように動作するプログラムは見つかりませんでした。ファイル記述子にxargs
何か問題があるのではないかと考え、またはに置き換えてみたり、さまざまな手探りの作業を試しました。xargs
xargs
bash -c 'kill -9 $$'
bash -c 'exec 0<&- 1<&-'
##mac
私は、、、、 Freenodeでも助けを求めました#macosx
が、誰も何が起こっているのか分かっていないようでした。##linux
#bash
Stack Overflowでも質問してみましたしかし、それはプログラミングとしては十分ではありませんでした。
> sw_vers | head -n 2
ProductName: Mac OS X
ProductVersion: 10.15.2
> zsh --version
zsh 5.7.1 (x86_64-apple-darwin19.0)
> bash --version | head -n 1
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin19)
> strings $(which xargs) | grep 'xargs.c'
$FreeBSD: src/usr.bin/xargs/xargs.c,v 1.57 2005/02/27 02:01:31 gad Exp $
> gxargs --version | head -n 1
xargs (GNU findutils) 4.7.0
答え1
興味深いキーワードを検索してxargs
実行することで、システムのソースコードを見つけることができました。すぐに少し古いバージョンのソースコードを見つけました。strings $(which xargs)
PROJECT:shell_cmds-207.40.1
shell_cmds-203
Appleのオープンソースサイト。
xargs
私はそのパッケージの のバージョンを でコンパイルしgcc -g *.c
、 を実行しecho | ./a.out > >(cat)
、デバッガをプロセスlldb
にアタッチしました。すると、からのa.out
の呼び出しでスタックしていることがわかりました(waitpid
xargs.c:610
ソース抜粋:
while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ?
WNOHANG : 0)) > 0) {
プログラムは複雑なのでxargs
、動作を再現できる小さな C プログラムを作りたいと思いました。これがそれです:
// tiny.c
#include <sys/wait.h>
int main() {
int status;
waitpid(-1, &status, 0);
return 0;
}
これを でコンパイルして を実行すると、と同様にハングしましたgcc tiny.c -o tiny
。実際、さらに単純化して はハングしますが、 はハングしません。echo | ./tiny > >(cat)
xargs
./tiny > >(cat)
( ./tiny; : ) > >(cat)
余談ですが、この小さなプログラムは Linux 上でコンパイルでき、Linux 上でこの動作を簡単に再現できます。
-1
に渡すとwaitpid
待機状態になります任意の子プロセス. そこで疑問が湧いてきます。tiny
には子プロセスがあるのに./tiny > >(cat)
、 にはないのはなぜですか( ./tiny; : ) > >(cat)
?
私は のソースコードを詳しく調べたことはありませんbash
が、何が起こっているかについては、かなり詳しい推測ができます。
まず、最初のコマンドを分析してみましょう: ./tiny > >(cat)
。最初にbash
名前付きパイプを作成し、次にfork()-exec()
をcat
子プロセスとして作成します。次に、その同じ名前付きパイプを自身のプロセスstdout
に設定します。最後に、 を呼び出して に変換するbash
ことで、その寿命を終えます。これで同じ PID を持ち、OS は依然としてそのプロセスを子プロセスと見なします。exec()
tiny
tiny
cat
重要なのは、 でも同じことが起きます( ./tiny ) > >(cat)
が、単にexec()
bash (括弧でサブシェルを開始) に入り、次に に入るというtiny
ことです。重要な事実は、bash
が実行するコマンドを 1 つだけ指定して起動された場合、 は ではなく、fork()-exec()
ただちに になるということですexec()
。
では、2 番目のコマンド を分析してみましょう: ( ./tiny; : ) > >(cat)
。最初は同じ結果になります:fork()-exec()
がcat
作成されます。次に が作成され、bash
exec()
新しいbash
インスタンスが作成されます。次に、実行するコマンドが 2 つあることがわかり、 が作成fork()-exec()
さtiny
れます。これはフォークされているため、この新しいプロセスは子プロセスをtiny
持たないため、ハングしません。次に が実行されます(は特殊な組み込みコマンドであるため、ここでは exec はありませんが、非組み込みコマンドを使用すると がフォークされるため、ハングは発生しません)。cat
bash
:
:
tiny