【问题标题】:Bash pipe to pythonBash管道到python
【发布时间】:2015-07-14 15:34:39
【问题描述】:

我需要通过管道实时吸收 bash 命令的输出。 例如

for i in $(seq 1 4); do echo $i; sleep 1; done | ./script.py

script.py 有这个

for line in sys.stdin.readlines():
        print line

我希望在序列可用时打印该序列,但 python 脚本正在等待 bash 脚本结束,然后再继续。

我查看了this 相关答案,但这并没有解决我的问题。我如何在 python 中实现这一点?

【问题讨论】:

  • readlines() 将所有行收集到一个列表中。直到它有所有的行,它才能进入列表。
  • 值得一读(或至少略读):stupidpythonideas.blogspot.com/2013/06/…
  • 糟糕,阿巴纳特打败了我。

标签: python linux bash shell


【解决方案1】:

第一个问题是readlines 将所有行读入一个列表。在所有行都存在之前,它不能这样做,直到 stdin 达到 EOF。

但您实际上并不需要 list 行,只需要 一些可迭代 行。还有一个文件,比如sys.stdin,已经这样一个可迭代的。而且它是一种懒惰的方法,只要它们可用就一次生成一行,而不是等待一次全部生成。

所以:

for line in sys.stdin:
    print line

每当您发现自己在寻找readlines 时,问问自己是否真的需要它。答案总是是否定的。 (好吧,除非你想用参数调用它,或者在一些有缺陷的不完全类似文件的对象上调用它。)请参阅Readlines Considered Silly 了解更多信息。


但与此同时,还有一个问题。并不是 Python 正在缓冲它的 stdin,或者其他进程正在缓冲它的 stdout,而是文件对象迭代器本身正在做内部缓冲,这可能(取决于你的平台——但在大多数 POSIX 平台上,它通常会)阻止您在 EOF 之前到达第一行,或者至少在读取很多行之前。

这是 Python 2.x 的一个已知问题,已在 3.x 中修复,* 但除非您愿意升级,否则这对您没有帮助。

命令行和环境文档以及大多数系统的手册页中都提到了该解决方案,但隐藏在 -u flag documentation 的中间:

请注意,xreadlines()、readlines() 和文件对象迭代器(“for line in sys.stdin”)中有内部缓冲,不受此选项影响。要解决这个问题,您需要在“while 1:”循环中使用“sys.stdin.readline()”。

换句话说:

while True:
    line = sys.stdin.readline()
    if not line:
        break
    print line

或者:

for line in iter(sys.stdin.readline, ''):
    print line

对于不同的问题,在this answer 中,Alex Martelli 指出您总是可以忽略sys.stdin 并重新fdopen 文件描述符。这意味着您将获得一个 POSIX fd 的包装器,而不是 C stdio 句柄。但这对于 this 问题来说既非必要也不充分,因为问题不在于 C stdio 缓冲,而在于 file.__iter__ 缓冲与其交互的方式。


* Python 3.x 不再使用 C stdio 库的缓冲;它自己做所有事情,在io 模块中的类型中,这意味着迭代器可以共享文件对象本身正在使用的同一个缓冲区。虽然 io 在 2.x 上也可用,但它不是 open 或 stdio 文件句柄的默认设置,这就是它在这里没有帮助的原因。

【讨论】:

  • 至少在 OS X 上,这仍然需要等待整个循环才能有 Python 的任何输出
  • 不是整个循环;在操作系统实际将任何数据写入 Python 脚本之前,足以填充管道缓冲区。
  • 如果您将 $(seq 1 4) 更改为 $(seq 1 100) 您将一直等待——至少在 OS X 上...
  • iter(sys.stdin.readline...) 为我工作(对于 seq 4 和 100)。非常感谢 abarnert。
【解决方案2】:

使用 Python 2.7.9(可能是所有 Python 3.x 之前的版本),这符合您的预期:

#!/usr/bin/python

import sys

while True:
   line=sys.stdin.readline()
   if not line:
      break
   print line   

你也可以这样做:

#!/usr/bin/python

import sys

for line in iter(sys.stdin.readline, ''):
   print line 

在 Python 3.4.3 上,您可以按照 abarnert 的建议进行操作:

#!/usr/local/bin/python3

import sys

for line in sys.stdin:
    print(line)

您也可以使用io class 重新打开 sys.stdin,就像 Python 3 使用的那样:

#!/usr/bin/python

import sys, io

for line in io.open(sys.stdin.fileno()):
    print(line)

第一个、第二个和最后一个方法都适用于 Python 2.7.6 和 2.7.9 以及 OS X 上的 Python 3.4.3;第三种方法,仅在 Python 3 上。

【讨论】:

  • @chepner 也不在 OS X 上的 Python 2.7.9 上。试试吧。它适用于您的系统吗?
  • 如果您阅读了 Alex Martelli 的回答,您不应该期望 -u 在这里提供帮助。仅仅因为它与不同的问题相关并不意味着它与这个问题相关。他明确引用的手册页片段告诉你它在这里不起作用,以及该怎么做......
【解决方案3】:

当前投票最多的答案实际上并未回答问题,因为它不会在流式传输时打印输出。像下面的代码应该做你想做的事:

import sys

def readline():
    while True:
        res = sys.stdin.readline()
        if not res:
            break
        yield res

for line in readline():
    print line

在这里,我们不是等待 readlines 构造一个列表,而是读取一行然后产生值。我们只是继续消费输入并让步,直到 sys.stdin.readline() 的空返回表示流结束。

【讨论】:

    猜你喜欢
    • 2023-03-26
    • 2022-01-10
    • 1970-01-01
    • 2013-05-03
    • 2016-08-22
    • 1970-01-01
    • 2012-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多