在python中运行shell命令并读取输出

时间:2014-07-16 10:35:44

标签: python unix

我有以下内容:

cmd = "ps aux | grep 'java -jar' | grep -v grep | awk '//{print $2}'".split(' ')
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
print out

当我在控制台中运行命令(在python之外)时,我得到了所需的输出。在python中运行上面的代码会打印一个空行。我假设cmd(特别是|运算符)存在一些问题,但我无法确定。

我需要使用标准的Python 2.6.6安装(无附加模块)来实现这一目标

3 个答案:

答案 0 :(得分:3)

您需要对原始命令的每一段使用Popen()一次调用,通过管道连接,如

import subprocess

p1 = subprocess.Popen(["ps", "aux"], stdout=subprocess.PIPE,  stderr=subprocess.PIPE)
p2 = subprocess.Popen(["grep", "java -jar"], stdin=p1.stdout, stdout=subprocess.PIPE,  stderr=subprocess.PIPE)
p3 = subprocess.Popen(["grep", "-v", "grep"], stdin=p2.stdout, stdout=subprocess.PIPE,  stderr=subprocess.PIPE)
p4 = subprocess.Popen(["awk", "//{print $2}"], stdin=p3.stdout, stdout=subprocess.PIPE,  stderr=subprocess.PIPE)
out, err = p4.communicate()

print out

subprocess documentation进行了深入的讨论。

答案 1 :(得分:2)

Popen默认只执行可执行文件不是shell命令行。 当您将参数列表传递给Popen时,他们应该使用其参数调用一个可执行文件:

import subprocess

proc = subprocess.Popen(['ps', 'aux'])

另请注意,使用str.split分割命令,因为:

>>> "ps aux | grep 'java -jar'     | grep -v grep | awk '//{print $2}'".split(' ')
['ps', 'aux', '|', 'grep', "'java", "-jar'", '', '', '', '', '|', 'grep', '-v', 'grep', '|', 'awk', "'//{print", "$2}'"]

请注意:

  • 引用的参数(例如'java -jar'))已被拆分。
  • 如果有多个连续的空格,你会得到一些空的参数。

Python已经提供了一个知道如何以合理的方式拆分命令行的模块,shlex

>>> shlex.split("ps aux | grep 'java -jar'     | grep -v grep | awk '//{print $2}'")
['ps', 'aux', '|', 'grep', 'java -jar', '|', 'grep', '-v', 'grep', '|', 'awk', '//{print $2}']

注意如何保留引用的参数,并优雅地处理多个空格。仍然无法将结果传递给Popen,因为Popen 默认将|解释为管道。

如果要运行shell命令行(即使用任何 shell功能,例如管道,路径扩展,重定向等),您必须通过shell=True。在这种情况下,你应该将一个字符串列表作为参数传递给Popen,但只有一个字符串才是完整的命令行:

proc = subprocess.Popen("ps aux | grep 'java -jar' | grep -v grep | awk '//{print $2}'", shell=True)

如果传递带shell=True的字符串列表,其含义不同:第一个元素应该是完整命令行,而其他元素作为选项传递给使用的shell 。例如,在我的机器上,默认shell(sh)有一个-x选项,它将在stderr上显示所有执行的进程:

>>> from subprocess import Popen
>>> proc = Popen(['ps aux | grep python3', '-x'], shell=True)
>>> 
username   7301  0.1  0.1  39440  7408 pts/9    S+   12:57   0:00 python3
username   7302  0.0  0.0   4444   640 pts/9    S+   12:58   0:00 /bin/sh -c ps aux | grep python3 -x
username   7304  0.0  0.0  15968   904 pts/9    S+   12:58   0:00 grep python3

在这里,您可以看到/bin/sh已启动,执行了ps aux | python3命令并且选项为-x

(这些都记录在Popen)的文档中。


这就是说,实现目标的一种方法是使用subprocess.check_output

subprocess.check_output("ps aux | grep 'java -jar' | grep -v grep | awk '//{print $2}'", shell=True)

然而,这在python< 2.7中不可用,因此您必须使用Popencommunicate()

proc = subprocess.Popen("ps aux | grep 'java -jar' | grep -v grep | awk '//{print $2}'", shell=True, stdout=subprocess.PIPE)
out, err = proc.communicate()

另一种方法是避免使用shell=True(通常是非常好的东西,因为shell=True引入了一些安全风险)并使用多个进程手动编写管道:

from subprocess import Popen, PIPE

ps = Popen(['ps', 'aux'], stdout=PIPE)
grep_java = Popen(['grep', 'java -jar'], stdin=ps.stdout, stdout=PIPE)
grep_grep = Popen(['grep', '-v', 'grep'], stdin=grep_java.stdout, stdout=PIPE)
awk = Popen(['awk', '//{print $2}'], stdin=grep_grep.stdout, stdout=PIPE)
out, err = awk.communicate()

grep_grep.wait()
grep_java.wait()
ps.wait()

请注意,如果您不关心标准错误,则可以避免指定它。然后它将继承当前进程之一。

答案 2 :(得分:1)

shell管道中只有一个命令很容易被Python代码替换。所以你可以启动几个外部进程并连接它们的输入和输出,但我只是启动ps aux并添加一些Python代码来过滤和提取所需的数据:

from subprocess import PIPE, Popen


def main():
    process = Popen(['ps', 'aux'], stdout=PIPE)
    pids = [
        line.split(None, 2)[1] for line in process.stdout if 'java -jar' in line
    ]
    process.wait()
    print '\n'.join(pids)


if __name__ == '__main__':
    main()