如何使用`subprocess`命令与管道一起使用

376

我想使用subprocess.check_output()ps -A | grep '进程名称'一起使用。我尝试了各种解决方案,但目前为止没有什么效果。请问有人可以指导我如何做吗?


4
有一个名为psutil的工具可以以便携的方式获取进程信息。 - jfs
我希望有一个既紧凑又可靠的解决方案。 - Sridhar Sarnobat
8个回答

611

要使用管道来操作subprocess模块,您必须传递shell=True参数。

不过,出于各种原因,尤其是安全方面的考虑,这并不是个好主意。相反,应该分别创建psgrep进程,并将一个进程的输出导入到另一个进程中,像这样:

ps = subprocess.Popen(('ps', '-A'), stdout=subprocess.PIPE)
output = subprocess.check_output(('grep', 'process_name'), stdin=ps.stdout)
ps.wait()

然而,在你的特定情况下,简单的解决方案是调用subprocess.check_output(('ps', '-A'))并在输出上运行str.find


137
将输出/输入分离以避免使用 shell=True,此举值得赞扬。 - Nicolas
10
不要忘记,错误 subprocess.CalledProcessError: Command('('grep', 'process_name')') 表示 grep 没有找到任何内容,因此这是正常行为。请注意,不要改变原意。 - Serge
6
既然已经有输出,为什么还需要ps.wait()呢?ps.wait.__doc__等待子进程终止,但子进程的内容似乎已经存储在 output变量中了。 - Papouche Guinslyzinho
3
@MakisH,您正在查看已被弃用的string.find,现在应该使用str.find(即str对象上的方法find)。 - Taymon
8
注意:如果grep过早结束,而且ps产生足够多的输出来填满其操作系统管道缓冲区(因为您没有在父进程中调用ps.stdout.close()),则ps可能会无限期地挂起。 交换起始顺序以避免此问题 - jfs
显示剩余9条评论

85

或者您始终可以在子进程对象上使用通信方法。

cmd = "ps -A|grep 'process_name'"
ps = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
output = ps.communicate()[0]
print(output)

communicate方法返回标准输出和标准错误的元组。


10
我认为使用 communicate 比使用 wait 更好。文档中有这样的警告:"当使用 stdout=PIPE 和/或 stderr=PIPE 且子进程生成足够的输出以至于阻塞等待操作系统管道缓冲区接受更多数据时,这将导致死锁。使用 communicate() 可以避免这种情况。" - Paolo
6
澄清Paolo上面的评论,警告是为了等待而不是为了沟通 - 即这就是他说沟通更好的原因。 - EnemyBagJones
在Python3中, ps.communicate()[0]的输出将返回一个字节对象。 - Miguel Ortiz
你正在重新发明 subprocess.check_output,虽然不算太糟糕但也不够优雅。正如文档所建议的那样,当库已经提供了高级函数来处理所有这些繁琐的事情,并通过一行代码实现时,应避免使用底层的 Popen,通常还能更好地处理边界条件。 - tripleee
你为什么要将标准错误重定向到“STDOUT”? - tripleee
将标准错误重定向到标准输出,可以确保捕获标准错误和标准输出。 - JvO

43

使用subprocess.run中的input可以将一个命令的输出传递到第二个命令中。

import subprocess
    
ps = subprocess.run(['ps', '-A'], check=True, capture_output=True)
processNames = subprocess.run(['grep', 'process_name'],
                              input=ps.stdout, capture_output=True)
print(processNames.stdout.decode('utf-8').strip())

6
注意:capture_output 只适用于 Python 3.7.9 及以上版本。 - MightyInSpirit
2
check 是用来做什么的?capture_output 的目的是什么? - CervEd
7
@CervEd 这两个选项都有明确的文档说明。capture_outputstdout=subprocess.PIPE, stderr=subprocess.PIPE 选项组合的简写形式,而 check=True 则会在子进程未返回成功(零)状态时引发错误。 - tripleee
2
@tripleee 它们在 Python 文档中的某个地方有记录,但回答中没有详细说明为什么要包含它们。例如,check=True 并非必须的,但 capture_output=True 是回答能够正常工作的原因之一。应该将使用这些选项的原因作为回答的一部分包含进来。 - CervEd
3
这种方法的缺点是capture_output会将进程的所有标准输出读入内存。对于像ps这样的小程序,这可能没问题,但对于更大的分析管道,应该避免使用此方法。 - Eldritch Cheese
显示剩余4条评论

31
请参考使用subprocess设置管道的文档:http://docs.python.org/2/library/subprocess.html#replacing-shell-pipeline 以下代码示例我未测试,但应该与您想要的大致相同:
query = "process_name"
ps_process = Popen(["ps", "-A"], stdout=PIPE)
grep_process = Popen(["grep", query], stdin=ps_process.stdout, stdout=PIPE)
ps_process.stdout.close()  # Allow ps_process to receive a SIGPIPE if grep_process exits.
output = grep_process.communicate()[0]

2
在检查失败后,请参考Taymon的答案,以获得不需要混乱操作即可正常工作的内容。 - Alvin
2
在Python 2.6.9中似乎不存在subprocess.check_output。 - RightmireM

4

你可以在 sh.py 中尝试管道功能:

import sh
print sh.grep(sh.ps("-ax"), "process_name")

3
链接已失效。 - tripleee
不需要了,链接已更新。 - cyraxjoe

3
此外,请尝试使用'pgrep'命令而不是'ps -A | grep 'process_name'

2
如果你想获得进程ID,显然。 - Shooe

2
command = "ps -A | grep 'process_name'"
output = subprocess.check_output(["bash", "-c", command])

3
为什么不使用 shell=True 并让它前置 ['sh', '-c'] 呢?这段代码中没有任何需要 bash 的地方。(话虽如此,最好的做法是尽量避免使用 shell;这个用例是合理的,但一旦参数开始被参数化——比如将 process_name 作为参数传递——安全问题就会出现)。 - Charles Duffy
1
什么?subprocess.check_output(command, shell=True)不需要你拆分字符串。Popen将任何字符串转换为只包含该字符串的列表--因此,[command]--所以使用shell=True,您会得到['sh', '-c']添加到该列表中,因此最终您会得到['sh', '-c', command],这正是您在此处的代码所做的,除了sh/bash之间的差异。 - Charles Duffy
1
事实上,如果您尝试将字符串拆分为列表并使用shell=True,那么只有该列表的第一个元素会被视为代码;您会得到类似于['sh', '-c', 'ps', '-A', '|', 'grep', 'process_name']的东西。这样做没有什么用:以这种方式调用时,shell会使用$0作为-A$1作为|等运行ps命令...但由于命令ps不查看$0$1等内容,所有额外的内容都会被忽略。 - Charles Duffy
我希望我能提供一个反例。这个答案存在是因为我找到了一个,但现在我不记得了。像这样做完美地运作。 - Brent
2
如果你阅读 Lib/subprocess.py,你会发现 subprocess.check_output(["sh", "-c", command])subprocess.check_output(command, shell=True) 之间 _实际上没有任何区别_。代码清晰简单 -- 这不是一个可能隐藏着魔鬼细节的地方。 - Charles Duffy
显示剩余2条评论

0

我认为仅仅为了享受管道而启动一个shell并不像它本应该的那么优雅。

下面的代码使用本地的subprocess管道支持,它确实起作用。

您可以轻松修改它以将更多的进程添加到管道中。

#!/usr/bin/env python3

import subprocess


def ps_grep(pattern):
    # First command-line
    ps_command = ["ps", "-A"]

    # Second command-line
    grep_command = ["grep", pattern]

    # Launch first process
    ps_process = subprocess.Popen(ps_command, stdout=subprocess.PIPE)

    # Launch second process and connect it to the first one
    grep_process = subprocess.Popen(
        grep_command, stdin=ps_process.stdout, stdout=subprocess.PIPE
    )

    # Let stream flow between them
    output, _ = grep_process.communicate()

    return output.decode()


if __name__ == "__main__":
    print(ps_grep("python"))

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