您可以创建自己的类似文件的类来写入多个文件句柄。这是一个简单的示例,其中包含重定向测试sys.stdout
and sys.stderr
.
import sys
class MultiOut(object):
def __init__(self, *args):
self.handles = args
def write(self, s):
for f in self.handles:
f.write(s)
with open('q1', 'w') as f1, open('q2', 'w') as f2, open('q3', 'w') as f3:
sys.stdout = MultiOut(f1, f2)
sys.stderr = MultiOut(f3, f2)
for i, c in enumerate('abcde'):
print(c, 'out')
print(i, 'err', file=sys.stderr)
运行该代码后,这些文件包含以下内容:
q1
a out
b out
c out
d out
e out
q3
0 err
1 err
2 err
3 err
4 err
q2
a out
0 err
b out
1 err
c out
2 err
d out
3 err
e out
4 err
FWIW,如果您愿意,您甚至可以这样做:
sys.stdout = MultiOut(f1, f2, sys.stdout)
sys.stderr = MultiOut(f3, f2, sys.stderr)
不幸的是,类似文件的对象MultiOut
不能与Popen
因为Popen
通过底层操作系统文件描述符访问文件,即,它需要操作系统认为是文件的东西,因此只有提供有效的Python对象fileno
方法可用于Popen
的文件参数。
相反,我们可以使用 Python 3asyncio执行 shell 命令并同时复制其 stdout 和 stderr 输出的功能。
首先,这是一个简单的 Bash 脚本,我用它来测试以下 Python 代码。它只是循环遍历一个数组,将数组内容回显到 stdout,将数组索引回显到 stderr,就像前面的 Python 示例一样。
多重测试.bsh
#!/usr/bin/env bash
a=(a b c d e)
for((i=0; i<${#a[@]}; i++))
do
echo "OUT: ${a[i]}"
echo "ERR: $i" >&2
sleep 0.01
done
output
OUT: a
ERR: 0
OUT: b
ERR: 1
OUT: c
ERR: 2
OUT: d
ERR: 3
OUT: e
ERR: 4
下面是运行 multitest.bsh 的 Python 3 代码,将其 stdout 输出通过管道传输到文件 q1 和 q2,并将其 stderr 输出传输到 q3 和 q2。
import asyncio
from asyncio.subprocess import PIPE
class MultiOut(object):
def __init__(self, *args):
self.handles = args
def write(self, s):
for f in self.handles:
f.write(s)
def close(self):
pass
@asyncio.coroutine
def copy_stream(stream, outfile):
""" Read from stream line by line until EOF, copying it to outfile. """
while True:
line = yield from stream.readline()
if not line:
break
outfile.write(line) # assume it doesn't block
@asyncio.coroutine
def run_and_pipe(cmd, fout, ferr):
# start process
process = yield from asyncio.create_subprocess_shell(cmd,
stdout=PIPE, stderr=PIPE, executable="/bin/bash")
# read child's stdout/stderr concurrently
try:
yield from asyncio.gather(
copy_stream(process.stdout, fout),
copy_stream(process.stderr, ferr))
except Exception:
process.kill()
raise
finally:
# wait for the process to exit
rc = yield from process.wait()
return rc
# run the event loop
loop = asyncio.get_event_loop()
with open('q1', 'wb') as f1, open('q2', 'wb') as f2, open('q3', 'wb') as f3:
fout = MultiOut(f1, f2)
ferr = MultiOut(f3, f2)
rc = loop.run_until_complete(run_and_pipe("./multitest.bsh", fout, ferr))
loop.close()
print('Return code:', rc)
运行代码后,这些文件包含以下内容:
q1
OUT: a
OUT: b
OUT: c
OUT: d
OUT: e
q3
ERR: 0
ERR: 1
ERR: 2
ERR: 3
ERR: 4
q2
OUT: a
ERR: 0
OUT: b
ERR: 1
OUT: c
ERR: 2
OUT: d
ERR: 3
OUT: e
ERR: 4
asyncio 代码是从J.F.塞巴斯蒂安的回答对这个问题Subprocess.Popen:将 stdout 和 stderr 克隆到终端和变量。谢谢,J.F!
请注意,当数据可供调度的协程使用时,数据就会写入文件;确切地when发生这种情况取决于当前的系统负载。所以我把sleep 0.01
multitest.bsh 中的命令使 stdout 和 stderr 行的处理保持同步。如果没有这种延迟,q2 中的 stdout 和 stderr 行通常不会很好地交错。可能有更好的方法来实现同步,但我仍然是异步编程的新手。