Python中从标准输入快速返回行

3
我正在制作一个脚本,将一些其他的脚本输出导入其中。另一个脚本需要一段时间才能完成,并在控制台上打印进度,以及我想要解析的数据。
由于我正在将结果导入我的脚本中,我希望能够做两件事情。当我的输入到达时,我想将其输出到屏幕上。在命令完成后,我希望得到通过stdin传递的行列表。
我的第一个想法是使用一个简单的
for line in sys.stdin:
     sys.stdout.write(line + '\n')
     lines.append(line)
     sys.stdout.flush()

但令我惊讶的是,该命令会等待 stdin 读取到 EOF 才开始逐行输出。

我的当前解决方法是:

line = sys.stdin.readline()
lines = []
while line:
    sys.stdout.write(line.strip() + '\n')
    lines.append(line.strip())
    sys.stdout.flush()
    line = sys.stdin.readline()

但这并不总是等到整个输入都被使用才执行。

还有其他的方法吗?看起来 for 循环的解决方案表现方式很奇怪。


我使用 | 进行管道操作。Mac OSX - Bartlomiej Lewandowski
你能澄清一下“这并不总是等到整个输入都被使用”是什么意思吗?你提供的解决脚本对我很有效。 - Aya
4个回答

3

Python使用缓冲输入。如果你使用python --help检查,你会看到:

-u     : unbuffered binary stdout and stderr; also PYTHONUNBUFFERED=x

所以尝试使用无缓冲选项:

command | python -u your_script.py

我会使用这个,但是当通过调用shebang解释器调用我的命令时,有没有一种方法可以传递这个参数? - Bartlomiej Lewandowski
1
@BartlomiejLewandowski:当然,我经常这样做。这里 你可以找到三种不同的方法来实现它。我通常使用 #!/usr/bin/python -u 的方式,但你可以选择适合你的方式。 - enrico.bacis
#!/usr/bin/python -u,注意,只有第一个参数可以这样识别。 - gilhad

3

编辑以回答关于输入结束时退出的问题

你描述的解决方法,或者类似下面的解决方法似乎是必要的:

#!/usr/bin/env python

import sys

lines = []

while True:
    line = sys.stdin.readline()
    if not line:
        break
    line = line.rstrip()
    sys.stdout.write(line + '\n')
    lines.append(line)
    sys.stdout.flush()

这在Python手册的-u选项下有详细说明:

   -u     Force stdin, stdout and stderr to  be  totally  unbuffered.   On
          systems  where  it matters, also put stdin, stdout and stderr in
          binary mode.  Note that there is internal  buffering  in  xread-
          lines(),  readlines()  and  file-object  iterators ("for line in
          sys.stdin") which is not influenced by  this  option.   To  work
          around  this, you will want to use "sys.stdin.readline()" inside
          a "while 1:" loop.
我创建了一个名为dummy.py的文件,其中包含上述代码,然后运行了以下命令:
for i in 1 2 3 4 5; do sleep 5; echo $i; echo; done | ./dummy.py

这是输出结果:

harold_mac:~ harold$ for i in 1 2 3 4 5; do sleep 5; echo $i; done | ./dummy.py
1

2

3

4

5

harold_mac:~ harold$

我怎样才能知道输入何时结束?行是否包含EOF标记? - Bartlomiej Lewandowski
我会使用 sys.stdin.closed 来查找 EOF。 - gilhad
1
如果管道中的第一个脚本输出空行,则编辑后的答案将无效。应该在 line = line.rstrip() 之前进行 if not line 检查。 - Aya
@Aya 谢谢,你是正确的。我已经通过空行验证了它只会在结尾停止。 - Harold Ship

1

其他人已经告诉您关于无缓冲输出的事情。我只想补充一些想法:

  1. often it is better to print debug info to stderr, and stderr output is usually unbuffered
  2. it is simplier to delegate intermediate output to special tools. For example, there is a tee utility, that allows to split stdout of a previous command. Assuming you are in bash, you can print the intermediate output to stdout right away, and use process substitution instead of printing to a file (instead of awk you will call your python script):

    $ python -c 'for i in range(5): print i+1' | tee >( awk '{print "from awk", $0**2 }')
    1
    2
    3
    4
    5
    from awk 1
    from awk 4
    from awk 9
    from awk 16
    from awk 25
    

0

你需要让你的Python程序中的1)stdin和2)管道对面的stdout都是行缓冲的。为了做到这一点, 1)在你的程序中使用stdin = os.fdopen(sys.stdin.fileno(), 'r', 1); 2)使用stdbuf -oL来改变另一个程序输出的缓冲模式:

stdbuf -oL otherprogram | python yourscript.py

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接