[Linux]如果管道被接受方关闭

linux shell中的管道|是非常方便的功能,可以将一个程序的输出作为另外一个程序的输入,这样我们可以将多个命令“拼”在一起,省去了临时文件的繁琐。windows中也有类似的用法,比如dir |more,学过dos命令的应该都知道吧。

既然是管道,那么就有一个入口和一个出口,各自对应一个应用程序,正常的情况下,入口应用程序的输出应当被出口应用程序全部接受,但在一些特殊情况,出口应用程序会提前关闭管道,比如在查询svn的更新日志,只取前己行的时候:

$ svn log |head
------------------------------------------------------------------------
r137 | Fwolf | 2007-05-28 13:38:47 +0800 (Mon, 28 May 2007) | 4 lines

更新记录。。。

svn: Write error: Broken pipe

由于head只需要用到输入的前10行(默认行数,也可由用户指定),再接收剩下的输出也是多余,便提前关闭了管道,管道入口的应用程序svn发现之后,便报错退出了。在这个例子中,错误信息非常清楚,但不是所有应用程序都这样的,比如下面这个:

$ find . -name "*rc" |xargs -i cat {}|head -1
[Desktop]
xargs: cat: terminated by signal 13

错误信息似乎并不太好理解,实际上它的意思是:xargs发现它的子进程cat由于信号13被中止了。由于xargs本身属于循环操作,发现错误之后就停止了循环,这是其一;信号13是在cat试图向一个已关闭的pipe管道中写数据的时候,系统产生的,cat收到之后就停止了。类似于在cat输出的过程中,用户按下ctrl+c的效果。

如何避免这种问题呢?很简单,管道后面使用不会提前关闭管道的程序即可,尤其是结合xargs使用的时候,它发现出错就不继续了。比如要用到head可以这样:

$ cat file |head -1

虽然cat仍然会被signal 13关闭,但bash是不会报错的,所以也只能针对一个文件进行操作,即使是使用了通配符也只能head到第一个文件。如果要加上对文件的遍历,可以用到for:

$for file in .*rc;do cat $file |head -1;done

cat依然会被关闭,但是for不会理会它,继续循环。head也可以直接指定文件名,这样我们就可以抛开cat了:

$find . -name "*rc" |xargs -i head -n1 {}

个人认为这是一种最完美的解决方式,即可以用到find强大的搜索指令,还不会涉及到管道的问题。不过如果文件名没有什么特殊要求,还有一种更简单的方式:

$head -n1 .*rc

在head的参数中直接用通配符指定文件,呵呵。

参考:

  • [蓝森林-管道和xargs的问题](http://www.lslnet.com/linux/dosc1/45/linux-312465.htm)
  • [why is child killed with signal 13](http://www.dbforums.com/archive/index.php/t-629393.html)

3 thoughts on “[Linux]如果管道被接受方关闭”

  1. 感谢你回答我的问题。你的答案有些深 :) 找机会我请你再解释一下。

    我原始的问题是有一个目录中有一些mp3,我需要重新压缩它们

    当我用

    ls *.mp3 |xargs -n 1 lame -b 64 --resample 44 --tt "名称" ... 和一大堆参数
    

    的时候,出现你说的这个错误

    我觉得这是因为我lame压缩重新压缩出来的文件名称后缀也是.mp3和原有文件重复了所以系统拒绝工作

    如果我把该目录下的mp3文件都改名为.test,谁能告诉我在命令行里面怎么改,我是用的xfce的thunar改的

    那么我再次运行上面的命令就可以了。但是注意,要明确告诉lame你输入的.test文件就是mp3文件,否则它当成输入的是wav了吧 ,所以你要加一个参数 –mp3input,这些 man lame就都知道可

    ls *.test |xargs -n 1 lame --mp3input -b 64 --resample 44 --tt "名称" ... 和一大堆参数
    

    请问有没有更好的解决方法?

  2. 把mp3文件放到一个目录中,然后在另外的一个目录里面进行操作 这样生成的文件就在当前目录中,不会和原文件冲突 就像:

    ls ../dir/*.mp3 |xargs ...
    

    或者用find替代ls,不管怎样,改名不是个好主意 🙂

  3. 我在学习进程间通信时,学习到了管道时对于一个管道一个父子进程总共关了4次,我十分想不通如下程序:

        #include
        #include
        #include
        #include
        #include
        #include

    int main()
    {
        int pipe_fd[2];
        pid_t pid;
        char buf_r[100];
        char *p_wbuf;
        int r_num;

    memset(buf_r, 0, sizeof(buf_r));
    
    /*创建管道*/
    if(pipe(pipe_fd)<0)
    {
        printf("pepe create error\n");
        return -1;
    }
    if((pid=fork()) == 0)
    {
        printf("\n");
        //close(pipe_fd[1]);
        sleep(2);
        if((r_num=read(pipe_fd[0], buf_r, 100))>0)
        {
            printf("%d number read from the pipe is %s\n", r_num, buf_r);
        }
        close(pipe_fd[0]);
        exit(0);
    }
    else if(pid>0)
    {
        //close(pipe_fd[0]);
        if(write(pipe_fd[1], "Hello", 5) != -1)
            printf("parent write1 Hello!\n");
        if(write(pipe_fd[1], " Pipe", 5) != -1)
            printf("parent write2 Pipe\n");
        close(pipe_fd[1]);
        sleep(3);
        waitpid(pid, NULL, 0);
        exit(0);
    }
    return 0;
    

    }

    上边是注释那两个就是,我不明白,不过我注释掉了,程序照样可以运行,我不明白一个管道不就一进一出嘛?

Leave a Reply

Your email address will not be published. Required fields are marked *