有些时候,我们需要在 Python 里执行一些 Bash / Shell 命令,比如调用外部的构建工具、Git 或者是其它程序。这时候,用 Python 自带的 subprocess 模块可以很方便的实现。在实际使用中,真正让人头大的有两点:如何执行像脚本一样运行多行 Shell 命令;以及如何实时得到命令的输出。
对于非常简单的 Shell 命令,只需要使用 subprocess.Popen 即可:
1
2
3
4
5
| import subprocess
process = subprocess.Popen(['echo', 'Hello World'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
|
上面的代码很好懂,即使用 Popen
开启一个新的进程执行命令,并将 stdout
和 stderr
通过 PIPE
记录。这个方式非常简单,适用于大多数情况。
但如果你的命令需要很长时间才能执行完成,会需要实时的输出。最简单的方式便是不将 stdout
和 stderr
通过 PIPE
记录,如此一来便无法得到 stdout
中的内容;此外,还可以通过 tee 命令配合 2>&1
,比如 echo hello 2>&1 | tee dev/tty
,这个方法可行但是对于每一行 Shell 命令都得使用。
经过了一番探索,最后找到了一个既可以执行多行 Shell 命令,并且能够得到实时输出的方法:
首先用 Popen
打开 /bin/bash
实例,将我们需要执行的 Shell 指令通过 stdin
传入,这样子做的好处是可以让 bash
逐行执行每一条命令。之后在一个 while
循环中,持续地读子进程 stdout
的内容,通过 proc.poll()
判断子进程是否终止来终止循环,最后通过 proc.wait()
得到子进程退出的代码,如果非 0 的话表示异常退出,进而选择下一步的操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| proc = subprocess.Popen(
"/bin/bash",
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
shell=True
)
proc.stdin.write(commands.encode())
proc.stdin.flush()
proc.stdin.close()
# Real time stdout of subprocess
stdout = []
while True:
line = proc.stdout.readline().decode()
stdout.append(line)
if line == "" and proc.poll() is not None:
break
# Print outputs
sys.stdout.write(line)
# Wait for child process and get return code
return_code = proc.wait()
|
这样便可以像 GitHub Action 一样 run 多行的命令了
1
2
3
4
5
6
7
| jobs:
say_hello:
runs-on: ubuntu-latest
steps:
- run: |
echo "Hello ${{ github.event.inputs.name }}!"
echo "- in ${{ github.event.inputs.home }}!"
|
参考: