stderr로의 리디렉션은 bash에서는 작동하지만 zsh에서는 작동하지 않습니다.

stderr로의 리디렉션은 bash에서는 작동하지만 zsh에서는 작동하지 않습니다.

배쉬에서는

❯ 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를 수신하고 종료됩니다.

프로세스의 스케줄링 순서에 따라 다르며, 왼쪽 프로세스 echotee가져오기 프로세스가 모두 오른쪽 프로세스보다 먼저 실행 되면 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제공 하지만 예를 들어 두 번째 출력 라인이 손실됩니다.helloworld

$ (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는 teeSIGPIPE 및 오류를 무시하는 것 같습니다.

$ 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"

관련 정보