【问题标题】:Python Popen hangs, yet same command in Windows cmd window runs finePython Popen 挂起,但 Windows cmd 窗口中的相同命令运行良好
【发布时间】:2017-12-06 12:00:34
【问题描述】:

我在这里拉头发。我正在生成一个需要 Python 反馈的过程。

当我在 cmd 窗口中运行命令时,它运行良好,但是当我尝试通过 Python 运行它时,终端挂起。

p = subprocess.Popen(startcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()

startcmd 是一个字符串,在 Python 控制台中打印时如下所示:

"C:/Program Files/GRASS GIS 7.2.1/grass72.bat" --version

如果我将其复制并粘贴到 Windows cmd 中,它会显示版本信息并在大约一秒钟后将控制权返回到命令提示符,但在 Python 中它会冻结。

我应该指出,如果我将startcmd 字符串替换为"dir" 甚至"python --version",它可以正常工作!

补充:我试过shell=True,结果一样。

附加:我已经尝试将 cmd 和参数作为数组发送,正如下面的答案中所建议的那样,shell=False,但这也同样挂起。

附加:我已将 GRASS 路径添加到系统 PATH,因此现在我可以在 cmd 窗口中简单地调用 grass72 --version 来获得结果,但这在 Python 中仍然会冻结,但是在 cmd 中运行良好。

补充:我创建了一个基本的 .bat 文件来测试 .bat 文件是否可以通过 Python 运行,这是我创建的:

@echo off
title Test Batch Script
echo I should see this message

这在 cmd 和 Python 中都可以正常运行。

发现但未解决的问题!

所以,我正在运行使用 Python 3.6 使用subprocess.Popen 生成进程的脚本。生成的 .bat 文件使用 GRASS 附带的 Python 版本(基于 2.7)启动 Python 脚本:

%GRASS_PYTHON% "\BLAH\BLAH\grass72.py"

有趣的是,如果我使用 Python 2.7 启动 subprocess.Popen 脚本,它可以正常工作。啊哈,你可能会想,解决了!但这并不能解决我的问题——因为我真的需要 Python 3.6 来启动该进程,为什么 Python 的哪个版本启动批处理文件很重要?无论如何,生成的新 Python 脚本是使用 Python 2.7 启动的。

自从我开始重定向标准输出后,我可以看到当我使用 Python 3.6 启动进程时出现错误:

 File "C:\ProgramData\Anaconda3\lib\site.py", line 177
    file=sys.stderr)
        ^
SyntaxError: invalid syntax

注意它恢复到 Anaconda3!即使它是使用 2.7 的 python.exe 启动的!

【问题讨论】:

  • 您正在运行的命令是否产生大量输出? subprocess.PIPE 可以容纳多少文本是有限制的,如果已满,它将阻止该过程。
  • 不,它比 dir 创建的要少得多。它只有一行。
  • 如果将startcmd 替换为cmd.exe /c "C:\\Program Files\\GRASS GIS 7.2.1\\grass72.bat" --version 会怎样?
  • 顺便说一句,如果您进行一些基本调试以确定: 1. 您观察到的行为是特定于 grass72.bat 还是打印输出显示的简单 .bat 脚本会很有用相同的行为。如果特定于grass72.bat,则尝试找出原因。 2.使用subprocess.PIPE是否重要。
  • 我实际上正在调查中,谢谢。首先我会尝试你的建议然后再回来。

标签: python cmd


【解决方案1】:

我在 Windows 上遇到了与 Python 3.6 和 3.7 相同的问题,因为子进程调用挂起:

p = subprocess.Popen(startcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()

经过仔细调查,我注意到只有当进程写入超过大约 4 KB(4096 字节)的输出时才会发生这种情况,这可能解释了为什么您的短脚本无法重现这一点。

我发现的一种解决方法是在标准库中使用tempfile

# Write to a temporary file because pipe redirection seems broken
with tempfile.NamedTemporaryFile(mode="w+") as tmp_out,
        tempfile.NamedTemporaryFile(mode="w+") as tmp_err:

    p = subprocess.Popen(startcmd, stdout=tmp_out, stderr=tmp_err,
        universal_newlines=True)

    # `run` waits for command to complete, `Popen` continues Python program
    while p.poll() is None:
        time.sleep(.1)

    # Cursor is after the last write call, reset to read output
    tmp_out.seek(0)
    tmp_err.seek(0)
    out = tmp_out.read()
    err = tmp_err.read()

【讨论】:

  • 非常感谢!我在 Windows 10 上的 setup.py 遇到问题,这似乎可以解决它。 (很惊讶你今天发布了这个,我今天需要它!!)
  • 另外,我发现建议使用communicate()。通过上述使用 tempfile 的修复,它可以正常工作。
【解决方案2】:

您没有在Popen 的参数中指定shell=True。在这种情况下,推荐的用法是指定参数序列而不是字符串。所以你应该设置startcmd等于["C:/Program Files/GRASS GIS 7.2.1/grass72.bat", "--version"]

【讨论】:

  • 我刚刚试过这个,恐怕同样的结果,它挂起。就像我使用我尝试过的 shell=True 一样。它在 cmd 提示符下的工作方式非常奇怪。
【解决方案3】:

试试这个:

p = subprocess.Popen(startcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)

【讨论】:

  • 已经试过了,谢谢,shell=True 显然是个坏主意。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-09
  • 1970-01-01
  • 1970-01-01
  • 2011-07-12
  • 1970-01-01
相关资源
最近更新 更多