让Python接收管道数据

Last Updated: 2023-04-06 07:08:55 Thursday

-- TOC --

程序代码自身并不知道stdin的输入来自哪里,tty或pipeline,只要读取stdin就行了。要让python代码接收来自pipeline的输入,就是读取stdin,但关键是不能让程序block,当stdin没有输入的时候,sys.stdin.read等一系列read函数接口,默认会陷入阻塞。

默认stdin和stdout是line buffered模式,并且是block模式,stderr是unbuffered,程序运行关键的异常或调试信息,建议都向stderr输出。

方法1:input接口

Python内置的input接口,可以按行读取stdin,每次调用input,它都会尝试以阻塞的方式从stdin读一行。

代码:

print(input())
print(input())

测试:

$ echo -e 'abcde\n12345' | python3 test_input.py
abcde
12345

$ echo 'abcde' | python3 test_input.py
abcde
Traceback (most recent call last):
  File "test_input.py", line 2, in <module>
    print(input())
EOFError: EOF when reading a line

$ echo -e 'abcde\n12345\nskipped' | python3 test_input.py
abcde
12345

echo命令的-e参数是必要的。

第1次测试正常,输入两行,用input读取两行。第2次测试,第2次调用input时读到了EOF,raise了。最后一个测试有3行数据输入,但是代码只调用两次input,最后一行数据就丢掉了。

方法2:O_NONBLOCK

Python代码直接读取stdin,关键是不能block,我们可以在程序的一开始,设置stdin为nonblock模式。

代码:

import os
import sys
from fcntl import *

print('stdin is blocking:',os.get_blocking(sys.stdin.fileno()))
fcntl(sys.stdin, F_SETFL, fcntl(sys.stdin,F_GETFL)|os.O_NONBLOCK)
print('stdin is blocking:',os.get_blocking(sys.stdin.fileno()))
print('readlines from stdin:', sys.stdin.readlines())

测试:

$ echo -e '123\nabc' | python3 pipe.py
stdin is blocking: True
stdin is blocking: False
readlines from stdin: ['123\n', 'abc\n']

$ python3 pipe.py
stdin is blocking: True
stdin is blocking: False
readlines from stdin: []

第2次测试,当stdin为空时,在nonblock模式下,也不会阻塞了。

注意:

  1. 使用readlines接口,一口气读取全部stdin的内容,如果stdin为空也不会出错,但是如果使用read函数,stdin为空的时候,会有个错误提示在python的stdlib内。
  2. F_GETFL和F_SETFL,FL是flags的意思;fcntl模块内还有F_GETFD和F_SETFD,FD是file descriptor的意思。

方法3:isatty

当Python程序通过管道接收数据时,stdin一定不是一个tty!只要判断stdin是否为tty,就可以不用设置stdin为nonblock模式。python有两种方法来判断stdin是否为tty:

>>> import sys
>>> sys.stdin.isatty()
True
>>> import os
>>> os.isatty(sys.stdin.fileno())
True

当stdin如果不是tty时,此时就直接读取stdin,获取来自pipeline的数据,而不需要设置stdin为nonblock,因为此时一定存在来自pipeline的重定向,否则stdin就是tty了。

代码:

import sys

if not sys.stdin.isatty():
    print(sys.stdin.read())
else:
    print('----')

测试:

$ python3 stdin_tty.py
----
$ echo -n '12345678' | python3 stdin_tty.py
12345678
$ python3 stdin_tty.py < kk.txt
abcdefg

方法4:select

使用select判断stdin是否有数据。

代码:

import sys
from select import select

if select([sys.stdin],[],[],0)[0]:
    print(sys.stdin.read())
else:
    print('====')

测试:

$ python3 stdin_select.py
====
$ echo -n '12345678' | python3 stdin_select.py
12345678
$ python3 stdin_select.py < kk.txt
abcdefg

本文链接:https://cs.pynote.net/sf/python/202201011/

-- EOF --

-- MORE --