為什麼‘迴聲| xargs >>(cat)` 掛在我的 Mac 上?

為什麼‘迴聲| xargs >>(cat)` 掛在我的 Mac 上?

這可以在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-203Apple 的開源網站

我使用、 ran編譯了xargs該套件中的版本,並將偵錯器附加到該進程。我發現它被困在對from (gcc -g *.cecho | ./a.out > >(cat)lldba.outwaitpidxargs.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 上重現此行為。

傳遞到-1waitpid導致它等待任何子進程。這就引出了一個問題:為什麼tiny有一個子進程./tiny > >(cat)卻沒有( ./tiny; : ) > >(cat)

我還沒有深入bash研究 的源代碼,但我對正在發生的事情有一個相當有根據的猜測。

首先讓我們來剖析第一個命令:./tiny > >(cat)。首先bash建立一個命名管道,然後作為子程序建立fork()-exec()cat然後它將自己的管道設置stdout為相同的命名管道。最後透過召喚變身bash結束了它的生命。現在具有相同的 PID,並且作業系統仍然認為該進程是其子進程。exec()tinytinycat

重要的是,同樣的事情也會發生,( ./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分叉,因此仍然不會出現任何掛起)。

相關內容