如果后跟管道,Linux tee命令有时会失败
我使用tee log和xargs进程输出运行find命令;无意中,我忘了在第二个管道中添加如果后跟管道,Linux tee命令有时会失败,linux,bash,tee,Linux,Bash,Tee,我使用tee log和xargs进程输出运行find命令;无意中,我忘了在第二个管道中添加xargs,并发现了这个问题 例如: % tree . ├── a.sh └── home └── localdir ├── abc_3 ├── abc_6 ├── mydir_1 ├── mydir_2 └── mydir_3 7 directories, 1 file 而a.sh的内容是: % cat a.sh
xargs
,并发现了这个问题
例如:
% tree
.
├── a.sh
└── home
└── localdir
├── abc_3
├── abc_6
├── mydir_1
├── mydir_2
└── mydir_3
7 directories, 1 file
而a.sh
的内容是:
% cat a.sh
#!/bin/bash
LOG="/tmp/abc.log"
find home/localdir -name "mydir*" -type d -print | tee $LOG | echo
如果我用一些命令添加第二个管道,例如echo
或ls
,写入日志操作偶尔会失败
以下是我多次运行/a.sh
时的一些示例:
% bash -x ./a.sh; cat /tmp/abc.log // this tee failed
+ LOG=/tmp/abc.log
+ find home/localdir -name 'mydir*' -type d -print
+ tee /tmp/abc.log
+ echo
% bash -x ./a.sh; cat /tmp/abc.log // this tee ok
+ LOG=/tmp/abc.log
+ find home/localdir -name 'mydir*' -type d -print
+ tee /tmp/abc.log
+ echo
home/localdir/mydir_2 // this is cat /tmp/abc.log output
home/localdir/mydir_3
home/localdir/mydir_1
为什么如果我用某个命令添加第二个管道(并且忘记
xargs
),则tee
命令偶尔会失败?问题是,默认情况下,当写入管道失败时,tee
退出。因此,考虑一下:
find home/localdir -name "mydir*" -type d -print | tee $LOG | echo
如果echo
首先完成,管道将失败,tee
将退出。不过,时间安排并不精确。管道中的每个命令都位于单独的子shell中。此外,缓冲也有变幻莫测的地方。因此,有时日志文件是在退出之前写入的,有时则不是
为了清楚起见,让我们考虑一个更简单的管道:
$ seq 10 | tee abc.log | true; declare -p PIPESTATUS; cat abc.log
declare -a PIPESTATUS='([0]="0" [1]="0" [2]="0")'
1
2
3
4
5
6
7
8
9
10
$ seq 10 | tee abc.log | true; declare -p PIPESTATUS; cat abc.log
declare -a PIPESTATUS='([0]="0" [1]="141" [2]="0")'
$
在第一次执行中,管道中的每个进程都以成功状态退出,并写入日志文件。在同一命令的第二次执行中,tee
失败,退出代码141
,日志文件未写入
我用true
代替echo
来说明echo
没有什么特别之处。任何可能拒绝输入的tee
之后的命令都存在问题
文档
最新版本的tee
具有控制管道故障退出行为的选项。从coreutils-8.25的man tee
--输出错误[=模式]设置写入错误的行为。请参阅下面的模式 模式的可能性包括: 模式确定输出上有写入错误的行为:
'warn' diagnose errors writing to any output
'warn-nopipe'
diagnose errors writing to any output not a pipe
'exit' exit on error writing to any output
'exit-nopipe'
exit on error writing to any output not a pipe
-p选项的默认模式是“warn nopipe”。默认值
未指定--output error时的操作是立即退出
在写入管道时出错,并诊断写入非管道时出错
产出
如您所见,默认行为是“立即退出”
写入管道时出错”。因此,如果在
tee
写入日志文件之前尝试写入tee
之后的进程失败,那么tee
将在不写入日志文件的情况下退出。我调试了tee
源代码,但我不熟悉Linux C,因此可能会出现问题
tee
属于coreutils包,位于src/tee.c
首先,它使用以下设置缓冲区:
setvbuf (stdout, NULL, _IONBF, 0); // for standard output
setvbuf (descriptors[i], NULL, _IONBF, 0); // for file descriptor
那么它是无缓冲的
第二,tee将stdout作为描述符数组中的第一个项,并将使用for循环写入描述符:
/* In the array of NFILES + 1 descriptors, make
the first one correspond to standard output. */
descriptors[0] = stdout;
files[0] = _("standard output");
setvbuf (stdout, NULL, _IONBF, 0);
...
for (i = 0; i <= nfiles; i++) {
if (descriptors[i]
&& fwrite (buffer, bytes_read, 1, descriptors[i]) != 1) // failed!!!
{
error (0, errno, "%s", files[i]);
descriptors[i] = NULL;
ok = false;
}
}
对,从T形三通到提前退出的某物的管道(在您的案例中不依赖于从T形三通读取输入)将导致间歇性错误。 有关此问题的摘要,请参阅:
为什么要在管道后添加回声。。我认为tee已经可以正常工作了。@LeeHoYo echo只是一个例子,我用xargs运行这个命令,但由于偶然的原因,我忘记了写xargs并发现了这个问题,所以我想知道为什么会导致这个问题。@TankyWoo,但是echo不读取
stdin
,所以一旦管道充满,它就会阻塞所有管道(或者如果使用echo internal bash命令,可能会出现错误,因为echo不读取任何内容)@LuisColorado阻塞管道是结果之一,例如将echo
替换为sleep 1
,但它可以写入文件。请参阅我的答案。@TankyWoo,如果将echo
替换为sleep 1
(也不从标准输入数据中读取数据)如前所述,一旦完成,内核将向其提供输入的进程发出信号,并返回一条断管消息。写入echo
的标准输入不会失败,直到echo
退出并且tee
尝试再次写入。echo
可能会很快退出在写入标准输出之前,tee
可能会花时间等待输入。@JonathanLeffler OK。这很可能在这里,我更新了答案。我还测试了seq 100000 | tee abc.log | sleep 10
,并获得了相同的间歇性结果,即使sleep
是长期存在的。在这种情况下,我觉得当睡眠输入缓冲区填满时,即使睡眠仍处于活动状态,tee也不能再写入,这会触发失败。当管道填满时,tee
将被阻止再写入管道(在上次write
调用中被阻止),直到“读取”过程(sleep
或echo
,两者均未实际读取其标准输入)读取某些内容,或终止(或以其他方式关闭其标准输入,因此没有剩余进程读取tee
的标准输出)@TankyWoo感谢您提供有关输出错误和coreutils版本的信息。答案已更新。至于管道,在Unix上,无论如何,不,进程不是连续运行的:它们是并行运行的。当使用管道提供连续输出时,这一点很重要。它还允许管道处理大量数据,而无需将数据写入每一步都使用磁盘。@TankyWoo:No-管道不是序列化执行。管道使用程序的并发执行。它必须;管道的容量有限且非常小(传统上为5 KiB,但在现代系统上通常为64 KiB),因此,如果流经管道的数据超过该大小,则程序必须同时执行,否则写入过程将被阻止。我很想对此投反对票,因为这基本上是一种转移视线的行为,但最后的见解是,这是一种浪费精力的行为,这是很有价值的,我相信这是出于好意而进行的.
write(1, "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n", 21) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=22649, si_uid=1000} ---
+++ killed by SIGPIPE +++