Python subprocess.call命令无法等待进程完成,针对的是Blender。

18

我在Blender中有一个Python脚本,其中包含

subprocess.call(os.path.abspath('D:/Test/run-my-script.sh'),shell=True)

其后是许多依赖于此Shell脚本完成的其他代码。问题在于它没有等待脚本完成,我不知道为什么?我甚至尝试使用Popen而不是call,如下所示:

p1 = subprocess.Popen(os.path.abspath('D:/Test/run-my-script.sh'),shell=True)
p1.wait()

我尝试使用沟通,但仍然没有起作用:

p1 = subprocess.Popen(os.path.abspath('D:/Test/run-my-script.sh'),shell=True).communicate()

这个shell脚本在MacOS上运行良好(修改路径后)并且在使用subprocess.call(['sh', '/userA/Test/run-my-script.sh'])时会等待。

但是在Windows上,当我在Blender中运行以下Python脚本时,一旦它到达子进程的那一行Git Bash就会被打开并运行shell脚本,而Blender不会等待它完成,只是在控制台中打印出Hello而已。有什么帮助吗?


Translated text:

This shell script runs well on MacOS (after modifying the paths) and waits when using subprocess.call(['sh', '/userA/Test/run-my-script.sh']).

However, on Windows, when I run the following Python script in Blender, once it reaches the subprocess line, Git Bash is opened and the shell script runs. Blender does not wait for it to finish, but only prints Hello in its console. Is there any help?

import bpy
import subprocess
subprocess.call(os.path.abspath('D:/Test/run-my-script.sh'),shell=True)
print('Hello')

1
当你在Windows命令行中运行D:/Test/run-my-script.sh时会发生什么?它会立即返回吗? - AShelly
你能发布一下Bash脚本吗? - Riccardo Petraglia
7个回答

9
您可以使用subprocess.call来实现这一点。

subprocess.call(args,*,stdin = None,stdout = None,stderr = None,shell = False,timeout = None)

运行由args描述的命令。等待命令完成,然后返回returncode属性。

编辑: 我想我已经猜到了问题出在哪里。该命令在您的Mac上可以正常工作,因为我认为Mac默认支持Bash(至少功能等效),而在Windows上它看到您尝试运行“.sh”文件,而是启动Git Bash,我假设在启动时执行了几个forks。
因此,Python 认为您的脚本已完成,PID已消失。
如果我是你,我会这样做:
  • 使用tempfile模块在“启动”脚本中生成一个唯一、不存在的绝对路径。
  • 启动脚本时,将刚刚创建的路径作为参数传递。
  • 当脚本启动时,在该路径处创建一个文件。完成后,删除该文件。
  • 启动脚本应监视该文件的创建和删除,以指示脚本的状态。
希望这样说得明白。

有时候这种方法不起作用。例如,在我的情况下,该进程是一个创建文件并访问该文件以在一段时间内修改其内容的程序。当文件被创建时,subprocess.call将进程视为已完成,而程序仍在修改文件的内容并且进程仍在进行中。是否有任何可用的解决方法? - SeF
@SeF 那么你正在做一些意外的事情——文档非常明确地说明了函数的工作方式。也许你正在分叉或执行到另一个进程? - joebeeson

8
您可以使用 Popen.communicate API。
p1 = subprocess.Popen(os.path.abspath('D:/Test/run-my-script.sh'),shell=True)
sStdout, sStdErr = p1.communicate()

该命令

Popen.communicate(input=None, timeout=None)

与进程交互:将数据发送到标准输入。读取标准输出和标准错误输出,直到达到文件结尾。等待进程终止。


感谢所有参与赏金的人,本答案提出了一种替代等待命令的方法,该方法似乎并不总是有效。 - SeF

2
使用 subprocess.PopenPopen.wait
process = subprocess.Popen(['D:/Test/run-my-script.sh'],shell=True, executable="/bin/bash")
process.wait()

你也可以使用check_call()而不是Popen。

2

subprocess.run 默认情况下会等待进程执行完毕。


你的问题让我感觉你正在使用 subprocess.Popen 而不是 subprocess.run,但是使用 run 时它不会等待吗? - Grr
我使用了subprocess.run但它没有等待。 - Tak
“as the first arg” 是什么意思? - Tak
在调用命令时,第一个参数是os.spawn()函数的参数,它告诉Python等待进程完成的旧方法。例如:os.spawn(os.P_WAIT, "run_my_script.sh") - Grr
没有 os.spawn,但有os.spawnl,spawnle,spawnv,spawnve - Tak
显示剩余2条评论

1

似乎有时运行命令会失败。这是我的解决方法:

def check_has_finished(pfi, interval=1, timeout=100):
    if os.path.exists(pfi):
        if pfi.endswith('.nii.gz'):
            mustend = time.time() + timeout
            while time.time() < mustend:
                try:
                    # Command is an ad hoc one to check if the process  has finished.
                    subprocess.check_output('command {}'.format(pfi), shell=True)
                except subprocess.CalledProcessError:
                    print "Caught CalledProcessError"
                else:
                    return True
                time.sleep(interval)
            msg = 'command {0} not working after {1} tests. \n'.format(pfi, timeout)
            raise IOError(msg)
        else:
            return True
    else:
        msg = '{} does not exist!'.format(pfi)
        raise IOError(msg)

1
您可以像这样使用os.system
import bpy
import os
os.system("sh "+os.path.abspath('D:/Test/run-my-script.sh'))
print('Hello')

os.system 在某些情况下很好用,但并非总是如此。它可能只在调用二进制文件时出现错误,因此建议使用 os.system 并不是很相关。 - David Peicho

0

可能有点冒险,但你是以管理员身份运行shell而Blender是以普通用户身份运行,还是反过来了?

长话短说(非常简短),Windows UAC是管理员和普通用户之间的一种隔离环境,因此可能会发生这样的随机问题。不幸的是,我记不得这个消息的来源了,最接近的是this

我的问题恰好与你的相反,wait()在无限循环中卡住了,因为我的Python REPL是从管理员shell启动的,无法读取普通用户子进程的状态。回到普通用户shell就解决了。这不是我第一次被这个UAC搞糊涂了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接