如何从Python脚本在后台执行Shell脚本

1 投票
2 回答
2172 浏览
提问于 2025-04-29 04:00

我正在尝试从Python中执行一个shell脚本,到目前为止一切正常。但我遇到了一点问题。

在我的Unix机器上,我使用&符号在后台执行一个命令。这个命令会启动我的应用服务器 -

david@machineA:/opt/kml$ /opt/kml/bin/kml_http --config=/opt/kml/config/httpd.conf.dev &

现在我想从我的Python脚本中执行同样的命令,但一旦执行了这个命令,它就不会进入else块,也不会打印出execute_steps::Successful,而是一直卡在那里。

proc = subprocess.Popen("/opt/kml/bin/kml_http --config=/opt/kml/config/httpd.conf.dev &", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, executable='/bin/bash')
if proc.returncode != 0:
    logger.error("execute_steps::Errors while executing the shell script: %s" % stderr)
    sleep(0.05) # delay for 50 ms
else:
    logger.info("execute_steps::Successful: %s" % stdout)

我是不是做错了什么?我希望在后台执行shell脚本后能打印出execute_steps::Successful

其他命令都能正常工作,但我尝试在后台运行的这个命令就是不行。

暂无标签

2 个回答

0

你正在使用 stderr=PIPE, stdout=PIPE,这意味着子进程的 stdinstdout 不会直接输出到当前进程的标准输出和错误流,而是被重定向到一个管道中。你需要在你的 Python 程序中通过 proc.stdoutproc.stderr 来读取这些输出。

如果你想让一个进程在后台运行,只需去掉 PIPE 的使用即可:

#!/usr/bin/python
from subprocess import Popen
from time import sleep

proc = Popen(
    ['/bin/bash', '-c', 'for i in {0..10}; do echo "BASH: $i"; sleep 1; done'])

for x in range(10):
    print "PYTHON: {0}".format(x)
    sleep(1)

proc.wait()

which will show the process being "backgrounded".
1

这里有几个事情需要说明。

首先,你在后台启动了一个命令行,然后又让这个命令行在后台运行程序。我不太明白你为什么觉得需要同时做这两件事,但我们先不讨论这个。实际上,当你在 shell=True 的基础上加上 executable='/bin/bash' 时,你实际上是在尝试用一个命令行去运行另一个命令行,再去运行后台程序,虽然这样并不太有效。*

其次,你使用了 PIPE 来处理程序的输出和错误信息,但却没有去读取它们。这可能会导致子进程出现死锁。如果你不想要输出,可以用 DEVNULL,而不是 PIPE。如果你想自己处理输出,可以使用 proc.communicate(),或者用一个更高级的函数,比如 check_output。如果你只是想让它和你自己的输出混在一起,那就直接不传这些参数。

* 如果你使用命令行是因为 kml_http 是一个不能直接执行的脚本,必须通过 /bin/bash 来运行,那么就不要为这个使用 shell=Trueexecutable,只需把 /bin/bash 放在命令行的第一个参数,/opt/kml/bin/kml_http 放在第二个参数。但这似乎不太可能;为什么要把一个不能执行的东西放到 bin 目录里呢?

** 或者你可以直接从 proc.stdoutproc.stderr 中读取,但这样会复杂一些。


总之,后台执行的主要目的是让它在后台持续运行,而你的脚本可以继续在前台运行。所以,你在它完成之前就检查它的 returncode,然后继续执行代码中的下一个部分,结果就再也不回来了。


看起来你是想等它完成。如果是这样,就不要在后台运行它——使用 proc.wait,或者直接用 subprocess.call(),而不是创建一个 Popen 对象。当然,也不要使用 &。顺便说一下,也不要用命令行:

retcode = subprocess.call(["/opt/kml/bin/kml_http",
                           "--config=/opt/kml/config/httpd.conf.dev"],
                          stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if retcode != 0:
    # etc.

现在,直到 kml_http 完成运行,你才会到达那个 if 语句。


如果你想等它完成,但同时又想做其他事情,那你就是想在程序中同时做两件事,这就需要一个线程来等待:

def run_kml_http():
    retcode = subprocess.call(["/opt/kml/bin/kml_http",
                               "--config=/opt/kml/config/httpd.conf.dev"],
                              stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if retcode != 0:
        # etc.

t = threading.Thread(target=run_kml_http)
t.start()
# Now you can do other stuff in the main thread, and the background thread will
# wait around until kml_http is finished and execute the `if` statement whenever
# that happens

撰写回答