问题
我一直在尝试编写一个程序来记录子进程的未捕获的异常和语法错误.容易,对吗?把stderr管道送到正确的地方.
但是,子进程是另一个python程序 – 我将其称为test.py-,它需要运行,就像它的输出/错误没有被捕获一样.也就是说,运行记录器程序需要看起来像用户正常运行python test.py.
进一步使问题复杂化的是the problem,如果不使用readline,raw_input实际上会被发送到stderr.不幸的是,我不能只导入readline,因为我无法控制使用我的错误记录器运行的文件.
笔记:
>我在这个代码运行的机器上相当受限制.我无法安装pexpect或编辑* customize.py文件(因为该程序将由许多不同的用户运行).我真的觉得应该有一个stdlib解决方案,但是…
>这只需要在mac上工作.
>这样做的动机是我是一个研究新程序员得到的错误的团队的一员.
我试过的
我尝试了以下方法,没有成功:
>只使用问题How do I write stderr to a file while using “tee” with a pipe?中的tee(未能生成raw_input提示);我在几个SO问题中发现的tee python实现有类似的问题
>覆盖sys.excepthook(无法使其适用于子进程)
> this question’s最佳答案似乎很有希望,但未能正确显示raw_input提示.
> logging module对于实际写入日志文件似乎很有用,但似乎没有解决问题的关键
>自定义stderr读者
>无休止的谷歌搜索
解决方法:
The tee-based answer that you’ve linked不太适合您的任务.虽然你可以通过使用-u选项来修复“raw_input()提示”问题来禁用缓冲:
errf = open('err.txt', 'wb') # any object with .write() method
rc = call([sys.executable, '-u', 'test.py'], stderr=errf,
bufsize=0, close_fds=True)
errf.close()
更合适的解决方案可能基于pexpect
或pty,example.
running the logger program needs to seem like the user has just run python test.py as normal.
#!/usr/bin/env python
import sys
import pexpect
with open('log', 'ab') as fout:
p = pexpect.spawn("python test.py")
p.logfile = fout
p.interact()
你不需要安装pexpect它是纯Python你可以把它放在你的代码旁边.
这是一个基于tee的模拟(test.py以非交互方式运行):
#!/usr/bin/env python
import sys
from subprocess import Popen, PIPE, STDOUT
from threading import Thread
def tee(infile, *files):
"""Print `infile` to `files` in a separate thread."""
def fanout(infile, *files):
flushable = [f for f in files if hasattr(f, 'flush')]
for c in iter(lambda: infile.read(1), ''):
for f in files:
f.write(c)
for f in flushable:
f.flush()
infile.close()
t = Thread(target=fanout, args=(infile,)+files)
t.daemon = True
t.start()
return t
def call(cmd_args, **kwargs):
stdout, stderr = [kwargs.pop(s, None) for s in 'stdout', 'stderr']
p = Popen(cmd_args,
stdout=None if stdout is None else PIPE,
stderr=None if stderr is None else (
STDOUT if stderr is STDOUT else PIPE),
**kwargs)
threads = []
if stdout is not None:
threads.append(tee(p.stdout, stdout, sys.stdout))
if stderr is not None and stderr is not STDOUT:
threads.append(tee(p.stderr, stderr, sys.stderr))
for t in threads: t.join() # wait for IO completion
return p.wait()
with open('log','ab') as file:
rc = call([sys.executable, '-u', 'test.py'], stdout=file, stderr=STDOUT,
bufsize=0, close_fds=True)
有必要合并stdout / stderr,因为不清楚raw_input(),getpass.getpass()可能会打印其提示.
在这种情况下,线程也不是必需的:
#!/usr/bin/env python
import sys
from subprocess import Popen, PIPE, STDOUT
with open('log','ab') as file:
p = Popen([sys.executable, '-u', 'test.py'],
stdout=PIPE, stderr=STDOUT,
close_fds=True,
bufsize=0)
for c in iter(lambda: p.stdout.read(1), ''):
for f in [sys.stdout, file]:
f.write(c)
f.flush()
p.stdout.close()
rc = p.wait()
注意:最后一个示例和基于tee的解决方案不捕获getpass.getpass()提示符,但pexpect和基于pty的解决方案执行:
#!/usr/bin/env python
import os
import pty
import sys
with open('log', 'ab') as file:
def read(fd):
data = os.read(fd, 1024)
file.write(data)
file.flush()
return data
pty.spawn([sys.executable, "test.py"], read)
我不知道pty.spawn()是否适用于macs.