這可以在zsh
和中重現bash
。
讓我更加困惑,echo | ( xargs; : ) > >(cat)
不會掛起。這也可以在zsh
和中重現bash
。
如果我使用 GNUxargs
提供的,brew install findutils
它不會掛起:echo | gxargs > >(cat)
。
事實上,除了我的系統之外,我還沒有發現任何其他程式xargs
具有這種行為方式。我認為xargs
文件描述符可能有問題,所以我嘗試在黑暗xargs
中用bash -c 'kill -9 $$'
或或許多其他鏡頭替換。bash -c 'exec 0<&- 1<&-'
##mac
我還在、#macosx
、##linux
和Freenode 上尋求幫助#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 的開源網站。
我使用、 ran編譯了xargs
該套件中的版本,並將偵錯器附加到該進程。我發現它被困在對from (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,並且作業系統仍然認為該進程是其子進程。exec()
tiny
tiny
cat
重要的是,同樣的事情也會發生,( ./tiny ) > >(cat)
但它只是exec()
進入 bash (括號啟動一個子 shell),然後進入tiny
.一個關鍵事實似乎是,當bash
啟動時僅執行一個命令,它不會立即執行fork()-exec()
,而是exec()
立即執行。
現在讓我們來剖析第二個命令:( ./tiny; : ) > >(cat)
。我們一開始就得到同樣的東西:fork()-exec()
存在cat
。然後bash
exec()
進入一個新bash
實例。然後它看到它有兩個命令要執行,所以它fork()-exec()
存在tiny
,並且因為它分叉,這個新tiny
進程沒有cat
子進程,所以它不會掛起。然後bash
執行:
(:
是一個特殊的內建函數,因此這裡沒有 exec,但使用非內建函數仍然會導致tiny
分叉,因此仍然不會出現任何掛起)。