处理Python中命令的连续输出

6 投票
2 回答
6115 浏览
提问于 2025-04-17 10:07

我刚开始学习Python,以前一直在用Perl。通常我在Perl中做的事情是打开一个命令并把它的输出赋值给一个本地变量,以便后续处理。换句话说:

"open CMD, "$command|";
$output=<CMD>;

这对我来说很简单。我想我可以在Python中用类似的方式做到这一点:

args=[command, args...]
process=subprocess.Popen(args, stdout=subprocess.PIPE)
output=process.communicate()

到目前为止都还不错。现在有个大问题……

如果我在多个平台上通过SSH启动那个命令,我就可以在Perl中使用选择循环来监控描述符,以便处理结果。虽然我找到了Python的select和poll模块,但不太确定怎么用。文档上说poll需要一个文件句柄,但当我试着把上面提到的'process'变量传给poll.register()时,出现了一个错误,提示必须是一个整数或者有fileno()方法。因为Popen()使用了stdout,我试着调用

poll.register(process.stdout)

结果不再报错,但程序就卡住了。

有没有什么建议或者指点,能让我实现类似的功能?

2 个回答

2
import subprocess

p = subprocess.Popen('apt-get autoclean', stdout=subprocess.PIPE, stderr = None, shell=True)

for line in iter(p.stdout.readline, ''):

    print line

p.stdout.flush()
p.stdout.close()

print ("Done")

当然可以!请把你想要翻译的内容发给我,我会帮你把它变得简单易懂。

8

使用 select.poll:你需要 传入带有 fileno 方法的对象或者真实的文件描述符(整数)

import os, sys, select, subprocess

args = ['sh', '-c', 'while true; do date; sleep 2; done']
p1 = subprocess.Popen(args, stdout=subprocess.PIPE)
p2 = subprocess.Popen(args, stdout=subprocess.PIPE)

while True:
    rlist, wlist, xlist = select.select([p1.stdout, p2.stdout], [], [])
    for stdout in rlist:
        sys.stdout.write(os.read(stdout.fileno(), 1024))

你会看到它每两秒暂停一次,然后根据可用数据产生更多输出。这里的“窍门”是 p1.stdout 是一个普通的文件样对象,它有一个 fileno 方法,可以返回文件描述符的编号。这正是 select 所需要的。

注意,我是通过 os.readstdout 读取数据,而不是简单地调用 stdout.read。这是因为像 stdout.read(1024) 这样的调用会让你的程序一直等待,直到读取到请求的字节数。只有在到达文件结束符(EOF)时,才会返回更少的字节,但由于 EOF 永远不会到达,stdout.read 的调用会一直阻塞,直到至少读取到 1024 个字节。

这与 os.read 函数不同,后者在可用字节少于请求时不会犹豫,它会立即返回可用的数据。换句话说,从 os.read(stdout.fileno(), 1024) 返回少于 1024 个字节并不意味着 stdout 已经关闭。

使用 select.epoll 几乎是一样的,只是你会得到一个“原始”的文件描述符(FD),需要用 os.read 来读取:

import os, sys, select, subprocess

args = ['sh', '-c', 'while true; do date; sleep 2; done']
p1 = subprocess.Popen(args, stdout=subprocess.PIPE)
p2 = subprocess.Popen(args, stdout=subprocess.PIPE)

poll = select.poll()
poll.register(p1.stdout)
poll.register(p2.stdout)

while True:
    rlist = poll.poll()
    for fd, event in rlist:
        sys.stdout.write(os.read(fd, 1024))

一个关闭的文件描述符会通过返回 select.POLLHUP 事件来表示。然后你可以调用 unregister 方法,最后在所有文件描述符都关闭时跳出循环。

最后,我想提到的是,你当然可以创建一个字典,将文件描述符映射回文件样对象,从而再映射回你启动的进程。

撰写回答