Это воспроизводится в zsh
и bash
.
Еще больше меня сбивает с толку, echo | ( xargs; : ) > >(cat)
не зависает. Это также воспроизводится в zsh
и bash
.
Если я использую GNU, xargs
как указано в brew install findutils
руководстве, то зависаний не происходит: echo | gxargs > >(cat)
.
Действительно, я не нашел ни одной другой программы, кроме моей системы, xargs
которая ведет себя таким образом. Я подумал, что, возможно, что-то xargs
происходит с дескрипторами файлов, поэтому я попробовал заменить xargs
на bash -c 'kill -9 $$'
или bash -c 'exec 0<&- 1<&-'
или много других снимков в темноте.
Я также искал помощи на ##mac
, #macosx
, ##linux
, и #bash
на Freenode, но никто там, похоже, не понимал, что происходит.Я также спросил на 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
from 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
завершает свою жизнь, вызывая exec()
преобразование в tiny
. Теперь tiny
имеет тот же PID, и ОС по-прежнему считает этот cat
процесс своим дочерним.
Важно то, что то же самое происходит с ( ./tiny ) > >(cat)
, но он просто exec()
s в bash (скобки начинают подоболочку), а затем в tiny
. Ключевым фактом, по-видимому, является то, что когда bash
запускается только с одной командой для выполнения, он не делает этого, fork()-exec()
а просто exec()
немедленно s.
Теперь давайте разберем вторую команду: ( ./tiny; : ) > >(cat)
. В начале мы получаем то же самое: fork()-exec()
ing cat
в существование. Затем bash
exec()
s в новый bash
экземпляр. Затем он видит, что у него есть две команды для выполнения, поэтому он fork()-exec()
в tiny
существование, и поскольку он разветвился, этот новый tiny
процесс не имеет cat
дочернего, поэтому он не зависает. Затем bash
выполняет :
( :
— это специальная встроенная функция, поэтому здесь нет exec, но использование не встроенной функции все равно приведет tiny
к разветвлению, поэтому зависания все равно не будет).