为什么这个 bash 命令在被 python 调用时不会运行?

为什么这个 bash 命令在被 python 调用时不会运行?

命令

$ find ~/foo/ -type f -iname "*.txt" -print0 | parallel -0 cat

用途GNU 并行打印.txt下的所有文件~/foo/

我有一个 python 脚本,我想在其中调用这个 bash 命令:

import subprocess, sys

def runBashCommand(my_command):
    process = subprocess.Popen(my_command.split(), stdout=subprocess.PIPE)
    output  = process.communicate()[0]
    return None

def makeCommand(my_path):
    return "find {} -type f -iname \"*.txt\" -print0 | parallel -0 cat".format(my_path)

发行

>>> makeCommand('~/foo/')

回报

'find ~/foo/ -type f -iname "*.txt" -print0 | parallel -0 cat'

但发出

>>> runBashCommand(makeCommand('~/foo/'))

产生错误

find: paths must precede expression: |
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

我的脚本有什么问题?

答案1

您实际上并没有运行bash命令。您正在做的是直接运行可执行文件并传递参数。

尝试以下脚本看看发生了什么:

import subprocess
p = subprocess.Popen(["echo", "a", "b", "|", "rev"], stdout=subprocess.PIPE)
print p.communicate()

输出将是:

('a b | rev\n', None)

没有发生重定向,“|”正在逐字传递。即,就好像您输入了find ... \| parallel ...。因此出现错误。

有两种方法可以修复它。

  • 最简单的方法:传递shell=Truesubprocess.Popen.这将通过 shell 运行它,以及所有需要的东西。如果这样做,您还需要传入一个字符串而不是数组:

    import subprocess
    p = subprocess.Popen("echo a b | rev", stdout=subprocess.PIPE, shell=True)
    print p.communicate()
    
    # Result: ('b a\n', None)
    

    如果你这样做,对字符串中的参数替换要非常小心

  • 稳健的方法:使用 Python 打开两个进程并将它们通过管道连接在一起。

    import subprocess
    # First command
    p1 = subprocess.Popen(["echo", "a", "b"], stdout=subprocess.PIPE)
    # Second command's input linked to the first one's output
    p2 = subprocess.Popen(["rev"], stdin=p1.stdout, stdout=subprocess.PIPE)
    # Read from p2 to get the output
    print p2.communicate()
    
    # Result: ('b a\n', None)
    

    这更加健壮,并且不会产生额外的 shell,但另一方面它的打字量也更大。如果你这样做,注意发生外壳替换。在你的情况下,它看起来不像你需要它,但如果你想使用,比如说~,你必须通过Python(例如os.getenv("HOME"))来获取它。

答案2

您不能split()使用命令字符串,因为它包含需要由 shell 处理的字符,例如~|。使用版本:

process = subprocess.Popen(my_command, stdout=subprocess.PIPE, shell=True)

相关内容