![重定向到 stderr 在 bash 中有效,但在 zsh 中无效](https://linux22.com/image/196979/%E9%87%8D%E5%AE%9A%E5%90%91%E5%88%B0%20stderr%20%E5%9C%A8%20bash%20%E4%B8%AD%E6%9C%89%E6%95%88%EF%BC%8C%E4%BD%86%E5%9C%A8%20zsh%20%E4%B8%AD%E6%97%A0%E6%95%88.png)
在巴什中,
❯ 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 的world
Bash 中,它实际上也只能打印。tee
无论如何,在我的 Debian 系统上。大多数时候。这里要注意的是,它echo
不会读取任何输入,并且可能会快速退出,比tee
从左侧管道获取输入并将其写入另一个管道的速度更快。然后,当tee
开始写入管道时,它会收到 SIGPIPE 并终止。但这是一场比赛。
也就是说,事件的顺序是这样的:
- 两者都
echo
打印它们打印的内容,并且都退出 tee
读取hello
并尝试写入管道- 它收到 SIGPIPE 并退出。
这取决于进程的调度顺序,如果左侧进程echo
和tee
获取进程都在右侧进程之前运行echo
,则没有问题。它还取决于tee
首先写入管道,但这似乎是tee
我拥有的 GNU 版本和 zsh 发生的情况。
检查后strace
我们看到tee
首先写入 fd 1 然后死亡:
$ 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 类似。在这里跟踪正确的进程比较困难,并且跟踪整个 shell 给出了所有相关进程的系统调用(交错的)。无论如何,有一个进程读取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 上,上面的管道为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
另一方面,Busyboxtee
似乎只是忽略 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"