子进程运行时输出混乱

2 投票
2 回答
1362 浏览
提问于 2025-04-16 12:39

我正在用以下代码来运行另一个Python脚本。现在遇到的问题是,这个脚本的输出顺序有点乱。

这里是一些输出
正在编辑xml文件并保存更改
正在上传xml文件..

但是当我通过命令行运行它时,输出是正确的,也就是上面这些内容。

到这里的输出是正确的
正在上传xml文件..
正在编辑xml文件并保存更改

这个脚本执行没有错误,也确实做了正确的更改。所以我觉得问题可能出在调用子脚本的那段代码上,但我找不到具体的问题在哪里:

    cmd = "child_script.py"
    proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
    (fout ,ferr) = ( proc.stdout, proc.stderr )
    print "Going inside while - loop"
    while True:
        line = proc.stdout.readline()
        print line
        fo.write(line)
        try : 
            err = ferr.readline()
            fe.write(err)
        except Exception, e:
            pass
        if not line:
            pass
            break

[编辑]: fo和fe是输出和错误日志的文件句柄。此外,这个脚本是在Windows上运行的。抱歉之前没有提到这些细节。

2 个回答

2

我发现输出的顺序有时候会因为执行的方式不同而变化。有些输出会发送到C语言的标准输入流stdin,而有些则会发送到标准错误流stderr。stdout和stderr的缓存特性会根据它们连接的对象(比如终端、管道、文件等)而有所不同:

NOTES
   The stream stderr is unbuffered.  The stream stdout is
   line-buffered when it points to a terminal.  Partial lines
   will not appear until fflush(3) or exit(3) is called, or a
   newline is printed.  This can produce unexpected results,
   especially with debugging output.  The buffering mode of
   the standard streams (or any other stream) can be changed
   using the setbuf(3) or setvbuf(3) call.  Note that in case
   stdin is associated with a terminal, there may also be
   input buffering in the terminal driver, entirely unrelated
   to stdio buffering.  (Indeed, normally terminal input is
   line buffered in the kernel.)  This kernel input handling
   can be modified using calls like tcsetattr(3); see also
   stty(1), and termios(3).

所以,也许你应该把stdout和stderr都配置成指向同一个地方,这样它们就会使用相同的缓存方式。

另外,有些程序会直接打开终端 open("/dev/tty",...)(主要是为了读取密码),所以比较终端输出和管道输出并不总是有效。

此外,如果你的程序同时使用了直接的 write(2) 调用和标准输入输出调用,那么输出的顺序可能会因为不同的缓存选择而有所不同。

希望这些信息中有一个是正确的 :) 如果有的话,请告诉我。

4

我觉得你引用的这段脚本有几个问题:

  • 正如detly提到的,fofe是什么?我猜这应该是你用来写子进程输出的对象吧?(更新:你提到这两个都是用来写输出日志的。)
  • 第三行有个缩进错误。(更新:我已经在原文中修正了。)
  • 你指定了stderr=subprocess.STDOUT,所以:(a) 在你的循环中,ferr会一直是None,(b) 由于缓冲的原因,标准输出和错误输出可能会以不可预测的方式混合在一起。不过,从你的代码来看,你似乎想要分别处理标准输出和标准错误,所以可以试试用stderr=subprocess.PIPE

按照jsbueno的建议,重写你的循环会是个好主意:

from subprocess import Popen, PIPE
proc = Popen(["child_script.py"], stdout=PIPE, stderr=PIPE)
fout, ferr = proc.stdout, proc.stderr
for line in fout:
    print(line.rstrip())
    fo.write(line)
for line in ferr:
    fe.write(line)

...或者进一步简化,因为看起来你的目标就是把子进程的标准输出和标准错误写入fofe,你可以直接这样做:

proc = subprocess.Popen(["child_script.py"], stdout=fo, stderr=fe)

如果你在fo写入的文件中仍然看到输出行被调换了,那我们只能假设在子脚本中可能有某种情况导致了这个问题。比如,子脚本是多线程的吗?某一行是通过另一个函数的回调打印出来的吗?

撰写回答