为什么subprocess.run输出与同一命令的shell输出不同?

use*_*536 9 c++ python subprocess io-redirection python-3.x

我正在使用subprocess.run()一些自动化测试.主要是自动执行:

dummy.exe < file.txt > foo.txt
diff file.txt foo.txt
Run Code Online (Sandbox Code Playgroud)

如果在shell中执行上述重定向,则这两个文件始终相同.但是无论何时file.txt太长,下面的Python代码都不会返回正确的结果.

这是Python代码:

import subprocess
import sys


def main(argv):

    exe_path = r'dummy.exe'
    file_path = r'file.txt'

    with open(file_path, 'r') as test_file:
        stdin = test_file.read().strip()
        p = subprocess.run([exe_path], input=stdin, stdout=subprocess.PIPE, universal_newlines=True)
        out = p.stdout.strip()
        err = p.stderr
        if stdin == out:
            print('OK')
        else:
            print('failed: ' + out)

if __name__ == "__main__":
    main(sys.argv[1:])
Run Code Online (Sandbox Code Playgroud)

这是以下C++代码dummy.cc:

#include <iostream>


int main()
{
    int size, count, a, b;
    std::cin >> size;
    std::cin >> count;

    std::cout << size << " " << count << std::endl;


    for (int i = 0; i < count; ++i)
    {
        std::cin >> a >> b;
        std::cout << a << " " << b << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

file.txt 可以是这样的:

1 100000
0 417
0 842
0 919
...
Run Code Online (Sandbox Code Playgroud)

第一行的第二个整数是后面的行数,因此这里file.txt将是100,001行.

问题:我是否误用了subprocess.run()?

编辑

评论后我的确切Python代码(换行符,rb)被考虑在内:

import subprocess
import sys
import os


def main(argv):

    base_dir = os.path.dirname(__file__)
    exe_path = os.path.join(base_dir, 'dummy.exe')
    file_path = os.path.join(base_dir, 'infile.txt')
    out_path = os.path.join(base_dir, 'outfile.txt')

    with open(file_path, 'rb') as test_file:
        stdin = test_file.read().strip()
        p = subprocess.run([exe_path], input=stdin, stdout=subprocess.PIPE)
        out = p.stdout.strip()
        if stdin == out:
            print('OK')
        else:
            with open(out_path, "wb") as text_file:
                text_file.write(out)

if __name__ == "__main__":
    main(sys.argv[1:])
Run Code Online (Sandbox Code Playgroud)

这是第一个差异:

在此输入图像描述

以下是输入文件:https://drive.google.com/open?id = 0B--mU_EsNUGTR3VKaktvQVNtLTQ

jfs*_*jfs 6

要重现,shell命令:

subprocess.run("dummy.exe < file.txt > foo.txt", shell=True, check=True)
Run Code Online (Sandbox Code Playgroud)

没有Python中的shell:

with open('file.txt', 'rb', 0) as input_file, \
     open('foo.txt', 'wb', 0) as output_file:
    subprocess.run(["dummy.exe"], stdin=input_file, stdout=output_file, check=True)
Run Code Online (Sandbox Code Playgroud)

它适用于任意大文件.

您可以subprocess.check_call()在这种情况下使用(从Python 2开始提供),而不是subprocess.run()仅在Python 3.5+中可用.

非常好,谢谢.但那么为什么原来失败了呢?管道缓冲区大小与Kevin Answer一样?

它与OS管道缓冲区无关.来自@Kevin J. Chase引用的子流程文档的警告与此无关subprocess.run().只有在使用process = Popen()并通过多个管道流()手动读取()/ write()时,才应该关心OS管道缓冲区process.stdin/.stdout/.stderr.

事实证明,观察到的行为是由于Universal CRT中的Windows错误造成的.这是在没有Python的情况下重现的相同问题:为什么重定向会在管道失效的地方工作?

错误描述中所述,要解决它:

  • "使用二进制管道并在阅读器端手动执行文本模式CRLF => LF翻译"ReadFile() 直接使用而不是使用std::cin
  • 或等待今年夏天的Windows 10更新(应该修复bug)
  • 或者使用不同的C++编译器,例如,如果在Windows上使用,则没有问题g++

该bug只影响文本管道,即使用的代码<>应该没问题(stdin=input_file, stdout=output_file应该仍然有用或者是其他一些bug).