【问题标题】:Running bash command in python [duplicate]在python中运行bash命令[重复]
【发布时间】:2022-01-28 19:21:06
【问题描述】:

我正在尝试运行一个包含多个管道的长 bash 命令。我做了一些研究,发现使用 os.chdir() 来切换到工作目录,并使用子进程来执行 bash 命令。

首先我需要切换到包含文件 sample.txt 的目录

os.chdir(path)

此路径将包含文件 sample.txt

然后我使用 subprocess 运行 bash 命令(对于 sed 命令,我将只使用一些东西):

p = subprocess.Popen("cat sample.txt | grep 'patternA'  | sed 'something' >> ~/out.txt", stdout=subprocess.PIPE, shell=True)
print p.communicate()

我打印出了('', None),当我检查~/out.txt 文件时,给出的bash 命令没有按预期打印任何内容(假设文件sample.txt 中存在patternA)

这是使用 subprocess 在 pythong 中运行 bash 命令的正确方法吗?感谢您的帮助!

【问题讨论】:

  • 嗯,现在我只是有点困惑如何捕获“cat sample.txt”的输出并将该输出通过管道传输到“grep'patternA',然后将新输出捕获到一个文件,全部使用子进程...

标签: python bash operating-system subprocess command


【解决方案1】:

您的代码:

import os, subprocess

with open("sample.txt", "w") as f:
    print("patternA1\npatternA2\nhello\nhi", file=f)

p = subprocess.Popen(
    "cat sample.txt | grep 'patternA' | sed 's/A/B/' > out.txt", 
    stdout=subprocess.PIPE, shell=True)

out.txt 已被填充。

>>> p.communicate()
(b'', None)
>>> print(open('out.txt').read(), end='')
patternB1
patternB2

当您使用 shell 并将输出直接重定向到文件时 - 您实际上是在进行 os.system() 调用。

>>> os.remove('out.txt')
>>> os.system("cat sample.txt | grep 'patternA' | sed 's/A/B/' > out.txt")
0
>>> print(open('out.txt').read(), end='')
patternB1
patternB2

【讨论】:

  • 就像文档已经说的那样,您可能应该更喜欢subprocess 而不是os.system,但如果您可以使用subprocess.check_call()或@等更高级别的函数之一,则可能不是特别是subprocess.Popen 987654330@。前者只比os.system() 长几个字符,但有几个好处。
【解决方案2】:

您的代码存在一些小问题,但总的来说,是的,这就是您运行子进程的方式。

  • 尽可能选择subprocess.run 而不是裸露的Popen。这也可以避免您认为令人困惑的行为;请参阅下一个要点。
  • 因此,如果您先运行Popen,然后运行communicate,则输出是一个具有两个值的元组,即标准输出和已完成进程的标准错误。
  • 但是您将标准输出重定向到一个文件,所以stdout=subprocess.PIPE 当然是不必要的并且什么也不会产生。 (因为你没有用stderr=subprocess.PIPE捕获stderr,所以它最终会包含None;如果有任何错误输出,它只是简单地显示给用户,而不是Python。)
  • 您的 shell 脚本过于复杂。将其减少为单个进程将避免需要shell=True,即generally something you should strive for.
  • 但更重要的是,该脚本可以在原生 Python 中重新实现,这将使其更加通用,并且对于不熟悉 shell 脚本和 Python 的任何人来说都更容易理解。 (当然,shell 的表述会更加简洁,至少在重构之后是这样。)

明显的 Python 实现如下所示

from pathlib import Path
...
with open("sample.txt", "r") as lines, \
        Path("~/out.txt").expanduser().open("w") as output:
    for line in lines:
        if "patternA" in line:
            output.write(line.replace('foo', 'bar'))

显然我们不得不猜测您的 sed 脚本实际上做了什么,因为您已将其替换为占位符。

subprocess.run 相同,并避免使用 shell 编程反模式,

from pathlib import Path
...
with Path("~/out.txt").expanduser.open("w") as output:
    subprocess.run(
        ['sed', '/patternA/something', 'sample.txt'],
        stdout=output, text=True, check=True)

您想避免使用 [无用的cat](useless use of catuseless grep; 并且不碍事,您不需要管道,因此不需要外壳。

如果您想从子流程中检索状态信息,请将subprocess.run 的结果分配给您可以检查的变量,例如r;错误状态将在r.resultcode 中(尽管check=True 保证为0)。

Python 不会让您将capture_output=Truestdout=... 和/或stderr=... 混合使用,因此如果您想查看是否有错误输出(即使某些工具成功,也可能会出现警告消息)您不得不拆分操作。可能是这样的:

import logging
from pathlib import Path
...
r = subprocess.run(
    ['sed', '/patternA/something', 'sample.txt'],
    capture_output=True, text=True, check=True)
with Path("~/out.txt").expanduser().open("w") as output:
    output.write(r.stdout)
if r.stderr:
    logging.warn(r.stderr)

最后,os.path.expanduser()pathlib.Path.expanduser() 是解析 ~/out.txt 到主目录中的文件所必需的。您通常永远不需要os.chdir() 来查找文件;如果它不在当前目录中,只需指定其路径名。另见What exactly is current working directory?

【讨论】:

猜你喜欢
  • 2014-12-01
  • 2021-03-22
  • 2011-05-14
  • 2018-04-03
  • 2020-01-10
  • 2016-05-30
  • 1970-01-01
  • 2016-02-20
相关资源
最近更新 更多