왜 `에코 | xargs > >(cat)`가 Mac에서 작동하지 않습니까?

왜 `에코 | xargs > >(cat)`가 Mac에서 작동하지 않습니까?

이는 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, #macosx, ##linux#bashFreenode에 대한 도움을 구했지만 그곳에서는 무슨 일이 일어나고 있는지 아는 사람이 아무도 없는 것 같았습니다.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.1shell_cmds-203Apple의 오픈 소스 사이트.

xargs해당 패키지 의 버전을 으로 컴파일하고 gcc -g *.c실행한 echo | ./a.out > >(cat)후 디버거를 프로세스 lldb에 연결했습니다 a.out. 나는 그것이 waitpidfrom 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()tinytinycat

중요한 것은 에서도 같은 일이 발생 ( ./tiny ) > >(cat)하지만 exec()bash(괄호로 서브셸 시작)에 들어간 다음 tiny. 중요한 사실은 bash실행할 명령이 하나만 있으면 시작될 때 실행되지 않고 fork()-exec()즉시 exec()실행된다는 것입니다.

이제 두 번째 명령을 분석해 보겠습니다 ( ./tiny; : ) > >(cat). 우리는 처음부터 같은 것을 얻습니다: 존재가 시작되는 fork()-exec()cat입니다. 그런 다음 bash exec()bash인스턴스로 들어갑니다. 그런 다음 실행해야 할 두 개의 명령이 있음을 확인하고 존재하게 되며 fork()-exec(), tiny분기되었기 때문에 이 새 tiny프로세스에는 하위 프로세스가 없으므로 cat중단되지 않습니다. 그런 다음 bash실행합니다 :( :는 특수 내장이므로 여기에는 exec가 없지만 내장되지 않은 것을 사용하면 여전히 tiny포크가 발생하므로 여전히 정지 현상이 발생하지 않습니다).

관련 정보