배쉬에서는
❯ echo "hello" 1>&2 | echo "world"
hello
world
zsh에서는
❯ echo "hello" 1>&2 | echo "world"
world
나는 이 문제를 해결하는 것이 아니라 왜 이런 일이 발생하는지 이해하려고 노력하고 있습니다. 여기서 작동하는 메커니즘은 무엇입니까?
답변1
이는 zsh가 다중 리디렉션을 허용하는 기능인 MULTIOS를 구현하는 방식과 관련된 것으로 보입니다. 예를 들어 실행하면
echo hello > abc > def
내부적으로 출력을 다음과 같이 변환하여 출력을 두 개의 파일로 복사합니다.
echo hello | tee abc def >/dev/null
행위
echo "hello" 1>&2 | echo "world"
그래서 비슷하다
echo "hello" | tee /dev/stderr | echo "world"
실제로 GNU coreuitls를 사용하는 Bash에서도 world
실제로는 인쇄만 합니다. tee
어쨌든 내 데비안 시스템에서. 대부분의 경우. 여기서 주목해야 할 점은 echo
입력을 읽지 않으며 tee
왼쪽 파이프에서 입력을 가져와 다른 파이프에 쓰는 것보다 더 빨리 종료할 수 있다는 것입니다. 그런 다음 tee
파이프에 쓰기가 시작되면 SIGPIPE를 수신하고 종료됩니다. 하지만 이것은 게임이다.
즉, 사건의 순서는 다음과 같습니다.
- 둘 다
echo
인쇄한 내용을 인쇄하고 둘 다 종료합니다. tee
파이프를 읽고hello
쓰려고 시도합니다.- SIGPIPE를 수신하고 종료됩니다.
프로세스의 스케줄링 순서에 따라 다르며, 왼쪽 프로세스 echo
와 tee
가져오기 프로세스가 모두 오른쪽 프로세스보다 먼저 실행 되면 echo
문제가 없습니다. 그것은 또한 tee
파이프에 먼저 쓰는 것에 달려 있지만 그것은 tee
내가 가지고 있는 GNU 버전과 zsh에서 일어나는 것 같습니다.
검사해 보면 fd 1이 먼저 작성된 다음 종료되는 strace
것을 볼 수 있습니다 .tee
$ echo "hello" | strace tee /dev/stderr | echo "world"
...
open("/dev/stderr", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
read(0, "hello\n", 8192) = 6
write(1, "hello\n", 6) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=20498, si_uid=1000} ---
+++ killed by SIGPIPE +++
zsh와 유사합니다. 여기에서는 올바른 프로세스를 추적하는 것이 어렵고, 전체 셸을 추적하면 모든 관련 프로세스의 시스템 호출이 제공됩니다(엇갈림). 어쨌든 그것을 읽는 프로세스가 있고 hello
즉시 어딘가에 쓰려고 시도하고 SIGPIPE를 얻는 프로세스가 있습니다.
$ strace -f zsh -c 'echo "hello" 1>&2 | echo "world"'
...
[pid 20503] read(14, "hello\n", 4092) = 6
[pid 20503] write(13, "hello\n", 6) = -1 EPIPE (Broken pipe)
[pid 20503] --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=20503, si_uid=1000} ---
[pid 20503] +++ killed by SIGPIPE +++
macOS에서 위 파이프는 sum 을 tee /dev/stderr
제공 하지만 예를 들어 두 번째 출력 라인이 손실됩니다.hello
world
$ (echo abc; sleep 2; echo def) | tee /dev/stderr | false
abc
이는 먼저 tee
쓰기를 한 다음 쓰기 파이프 오류로 인해 종료되고 두 번째 행을 쓸 수 없게 되는 것과 /dev/stderr
일치합니다 . 하지만 strace
세부정보를 볼 수 있는 비슷한 도구가 있는지는 모르겠습니다 .
read
여기서 첫 번째 줄은 읽은 후 문제 없이 전달되지만 두 번째 줄은 다시 손실됩니다.
$ zsh -c '(echo abc; sleep 2; echo def) 1>&2 | read'
abc
GNU 매뉴얼 페이지에는 파이프 쓰기 오류 종료에 대해서도 tee
언급되어 있습니다 .tee
지정되지 않은 경우 기본 작업은
--output-error
파이프에 쓰는 동안 오류가 발생하면 즉시 종료하고 비파이프라인 출력에 쓰는 동안 오류를 진단하는 것입니다.
옵션을 설정한 후 SIGPIPE를 무시하고 오류를 극복하고 계속합니다.
$ echo "hello" | tee --output-error=warn /dev/stderr | echo "world"
world
tee: 'standard output': Broken pipe
hello
반면 Busybox는 tee
SIGPIPE 및 오류를 무시하는 것 같습니다.
$ echo "hello" | strace busybox tee /dev/stderr | echo "world"
world
...
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x412030}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
openat(AT_FDCWD, "/dev/stderr", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
read(0, "hello\n", 1024) = 6
write(1, "hello\n", 6) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=11700, si_uid=1000} ---
write(3, "hello\n", 6hello
) = 6
read(0, "", 1024) = 0
exit_group(0) = ?
+++ exited with 0 +++
어쨌든, 어떤 입력도 읽지 않는 대상에 파이프를 연결하는 것은 아마도 약간 어리석은 일입니다. 이와 같은 것은 echo
둘 다 독립적으로 실행됩니다.
echo "hello" 1>&2 & echo "world"