相信很多在linux平台上工作的童鞋都对管道符号'|'非常熟悉,通过它我们可以灵活组合几个不同的命令来完成一个任务。如下命令:echo123|awk'{print$0+123}'#Output246不过这次我们不讲这些用法,而是讨论一些比较有意思的,也就是管道两边数据流的“实时”以及流水线的使用技巧。其实,我们在使用管道的时候,可能会不经意地想,我上一条命令的输出是应该处理后通过管道传递给第二条命令,还是边处理边输出?也许在每个人都在实验或工作经历中。应该是把左边的命令全部处理完,再交给右边的命令处理。不光是你,我在刚接触管道的时候也有这样的误区,因为我们通过现象看穿了Thisiswhatyouarearounded。但其实只要对pipeline工具有一个简单的了解,应该不难得到一个解释:pipeline的两侧是同时执行的,也就是说在pipeline上的命令left是输出到pipeline,pipeline的右边会立即处理。管道管道的定义就是一个由内核管理的缓冲区,相当于我们放在内存中的一个音符。管道的一端连接到进程的输出。此过程将消息放入管道。管道的另一端连接到一个进程的输入,该进程获取放入管道的信息。一个缓冲区不需要很大,它被设计成一个循环的数据结构,这样流水线就可以循环使用。当管道中没有消息时,从管道中读取的进程会一直等待,直到另一端的进程放入消息。当管道充满消息时,试图放入消息的进程将阻塞,直到另一端的进程获得消息。当两个进程都终止时,管道自动消失。从上面的解释可以看出,pipelineworkflow流程图假设COMMAND1|COMMAND2,那么COMMAND1的标准输出会绑定到管道的写端,而COMMAND2的标准输入会绑定到管道的读端,所以COMMAND1一有输出就送到COMMAND2立即通过管道。我们做个实验来验证一下:#1.pyimporttimeimportsyswhile1:print'1111'time.sleep(3)print'2222'time.sleep(3)[root@iZ23pynfq19Z~]#python1|cat在上面的命令中,我们可以猜测输出结果:休眠6秒后,输出“1111222”,或者输出“1111”,休眠3秒,然后输出“2222”,再休眠3秒,以及然后输出“1111”?答案是:都不是!什么!这是不可能的,你可以试试看,我们会看到终端没有反应,为什么?这里涉及到文件IO的缓冲方式,关于文件IO可以参考我的另一篇文章:浅谈文件描述符1和2,文件IO的三种缓冲方式在最下面提到:fullbuffering:untilthebufferisthesystemI/O函数只有满了才会调用,(通常是文件)linebuffering:遇到换行符时输出(标准输出)Unbuffered:没有缓冲,数据会立即读入或输出到外部存储文件和设备(标准错误是因为python默认使用bufferedfputs(参考py27源码:fileobject.c:PyFile_WriteString函数),并且因为标准输出重写到管道,所以会采用fullbuffer方式(shell命令依赖在实现上,因为有的是用unbufferedwrite实现的,如果没有buffer就直接写入pipeline),所以会采用fullbuffer的方式,即buffer写满,或者手动显示和调用flush以查看输出。那么我们可以把代码改写成下面两种方式#方式一:填充缓冲区,这里的大小是4096字节,你也可以试试这个值,估计是一样的importtimeimportsyswhile1:print'1111'*4096次。睡眠(3)打印'2222'*4096time.sleep(3)#方法二:手动flush入writequeueimporttimeimportsyswhile1:print'1111'sys.stdout.flush()//因为是标准输出,所以直接通过sys接口flushtime。sleep(3)print'2222'sys.stdout.flush()time.sleep(3)输出结果:#第一种方法:[root@iZ23pynfq19Z~]#python1|cat1111.....(超多1,刷屏..)sleepfor3seconds..2222.....(太多2,刷屏..)#第二种方式:[root@iZ23pynfq19Z~]#蟒蛇1|cat1111睡眠3秒。.2222Sleepfor3seconds..1111...到这里我们已经能够得到结果了。如果像我们之前想的那样,要等到COMMAND1执行完,才一次性输出到COMMAND2,那么结果应该是无限阻塞。..因为我的程序还没有执行..这应该不符合老前辈的设计初衷,因为可能会导致管道越来越大..不过管道也是有大小的~可以参考下posixstandardfordetails,所以我们得出结论,只要将COMMAND1的输出写到管道的写端(不管buffer满了还是手动flush),COMMAND2就会马上拿到数据,马上处理。然后讨论了管道两侧数据流的“实时性”。告一段落,在此基础上继续讨论:管道使用技巧。在开始讨论之前,先介绍一个我们偶尔会遇到的技术术语,那就是:SIGPIPE或者更具体的描述:Brokenpipe(破管)以上专业术语与管道的读写规则密切相关,那么我们看一下管道的读写规则:当没有数据可读时O_NONBLOCK(未设置):读调用阻塞,即进程暂停执行,等待数据到来。O_NONBLOCK(设置):read调用返回-1,errno值为EAGAIN。当管道满时O_NONBLOCK(未设置):write调用阻塞,直到有进程读取数据O_NONBLOCK(设置):调用返回-1,errno值为EAGAIN管道被关闭,则读返回0如果管道读端对应的所有文件描述符都被关闭,写操作会产生信号SIGPIPE,当要写入的数据量不大于PIPE_BUF时,Linux会保证写的原子性。当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。上面我们可以看到,如果我们收到一个SIGPIPE信号,一般情况是读端关闭了,但是写端还是会尝试写给我们重现SIGPIPE#!/usr/bin/pythonimporttimeimportsyswhile1:time。sleep(10)#手速不够快的童鞋可以把休眠时间设置长一些print'1111'sys.stdout.flush()这次执行命令需要测试手速,因为我们要抓py唤醒前,kill读结束进程python1|cat----------------------#另一个终端[root@iZ23pynfq19Z~]#ps-fe|grep-P'cat|python'root107754074000:05pts/200:00:00python1root107764074000:05pts/200:00:00cat#读取结束进程root1083332581000:06分/000:00:00grep-Pcat|python[root@iZ23pynfq19Z~]#kill10776输出[root@iZ23pynfq19Z~]#python1|catTraceback(mostrecentcalllast):File"1",line6,in
