【问题标题】:Strange execution patterns with subprocess.Popensubprocess.Popen 的奇怪执行模式
【发布时间】:2018-03-20 17:33:29
【问题描述】:

我有一个 Python 脚本,其中调用了一个 JAR。调用 JAR 后,会调用两个 shell 脚本。最初我是这样做的:

proc = subprocess.Popen(jar_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proc.wait()
output, errors = proc.communicate()

proc = subprocess.Popen(prune_command, shell=True)
proc.wait()

proc = subprocess.call(push_command, shell=True)

我必须等待前两个进程完成,所以我使用Popen(),最后一个我可以让它在后台运行,所以我call() 它。我通过 shell=True 是因为我希望被调用的 shell 脚本能够访问环境变量。

上述方法有效,但是,我没有从 JAR 进程中获得任何日志记录。我试过这样称呼它:

proc = subprocess.call(jar_command)

此日志如我所料,但未执行后面的两个 shell 脚本。最初我认为日志不会发送到stdout,但事实证明它们根本没有被执行。 IE。不删除多余的文件或推送到数据库。

为什么后续的 shell 脚本会被忽略?

【问题讨论】:

  • 看来您可能使用了 Popencall 颠倒了 - call 将等待子进程终止。
  • 如果缓冲区填满,您对.wait() 的使用可能会导致死锁。
  • 我通常更喜欢check_call 而不是call,因为如果返回非零退出代码,它会抛出异常。
  • 我会通过 Python 脚本添加日志记录,以验证每个进程是否按预期执行和终止。我还会检查每个的输出和返回代码,以验证它们是否符合预期。
  • 环境变量不需要shell=True,除非你指的是来自BASH_ENV的变量。

标签: java python bash shell


【解决方案1】:

您很可能忘记了进程流实际上是具有有限容量的操作系统级缓冲区。

例如,如果您在 PIPE 模式下运行一个产生大量输出的进程,并且在尝试使用该进程写入输出的任何内容之前等待它完成,您就会陷入死锁:

  • 进程已填满输出缓冲区,现在无法将更多数据写入其输出。在有人通过管道读取清空缓冲区之前,该过程无法继续。
  • 在您从缓冲区读取数据之前,您的程序正在等待子进程完成。

正确的方法是在您的程序中启动一个线程,该线程将在进程运行和主线程等待时不断“排出”管道。您必须先启动进程,然后启动排水线程,然后等待进程完成。

对于鉴别诊断,检查子进程是否会在输出很少的情况下正常运行(即,只要缓冲区没有填满,例如一两行)。

subprocess 的文档有一个note 关于这个。

【讨论】:

    【解决方案2】:

    如果您确定您的 shell 脚本根本没有运行,并且使用第一个代码一切正常 - 那么它 必须是 java 命令死锁或无法正确终止使用call() 函数。

    您可以通过在 bash 脚本中添加虚拟文件创建来验证这一点。将它放在脚本的第一行,因此如果执行它,您将获得创建的虚拟文件。如果它没有被创建,这意味着脚本没有被执行,可能是由于 java 执行的原因。

    我会尝试几件事:

    首先我会返回Popen 而不是call。不要使用wait(),而是使用communicate()

    与进程交互:将数据发送到标准输入。从 stdout 和 stderr 读取数据,直到到达文件结尾。 等待进程终止communicate() 返回一个元组 (stdoutdata, stderrdata)

    proc = subprocess.Popen(jar_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    proc.communicate()
    

    确保检查两个流的数据(stdout 和 stderr)。您可能会错过 java 进程引发的错误。

    接下来我会尝试通过将bufsize=0 提供给Popen 来禁用缓冲区。它将消除与 python 缓冲相关的选项。

    如果这两个选项仍然不起作用,请尝试使用check_call()查看是否有异常:

    proc = subprocess.check_call(jar_command)
    

    运行带有参数的命令。等待命令完成。如果返回码为零则返回,否则 raise CalledProcessError

    这些选项可能有答案;如果没有,它们将有助于调试过程。随意评论这个进展如何。

    【讨论】:

      猜你喜欢
      • 2022-08-23
      • 1970-01-01
      • 1970-01-01
      • 2011-12-26
      • 1970-01-01
      • 1970-01-01
      • 2021-10-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多