如果后面跟着一个pipe道,Linux tee命令偶尔会失败

我用tee log和xargs处理输出运行find命令; 我偶然忘记在第二个pipe道中添加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 

如果我用一些命令(如echols添加第二个pipe道,写入日志操作有时会失败。

以下是一些运行./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 

为什么如果我用一些命令添加第二个pipe道(忘记xargs ), tee命令偶尔会失败?

问题是,默认情况下,当写入管道失败时, tee退出。 所以,考虑一下:

 find home/localdir -name "mydir*" -type d -print | tee $LOG | echo 

如果echo首先完成,管道将失败, tee将退出。 但是,时机并不准确。 管道中的每个命令都在一个单独的子shell中。 另外,还有变幻莫测的变化。 所以,有时日志文件是在tee退出前写入的,有时却不是。

为了清楚起见,让我们考虑一个更简单的管道:

 $ 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有一个选项来控制管道故障退出行为。 从coreutils-8.25:

–output误差[= MODE]
在写入错误时设置行为。 请参阅下面的MODE

MODE的可能性是:

MODE决定输出写入错误的行为:

  '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将不写入日志文件而退出。

正确的,从发球台到提前退出(不依赖于你的情况下的发球台输入)将导致间歇性错误。 有关这个问题的总结,请参阅:

http://www.pixelbeat.org/docs/coreutils-gotchas.html#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; } } 

tee a.log ,描述符[0]是stdout,描述符[1]是a.log。

正如@ John1024所说,流水线是并行的 (我之前所误解的)。 第二个pipe命令,比如echols或者true不接受input ,所以不会“等待”输入 ,如果执行的更快,在tee写入输出结束之前会关闭管道( 输入端 ) ,所以上面的代码,注释行会失败,不会继续写入文件描述符。


供应:

killed by SIGPIPE strace结果:

 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 +++