启动包含管道命令的子进程时发现文件未找到错误

时间:2014-06-19 12:05:47

标签: python shell subprocess pipe

我需要在我的localhost上使用Python运行命令date | grep -o -w '"+tz+"'' | wc -w。我正在使用subprocess模块并使用check_output方法,因为我需要捕获相同的输出。

但是它给我一个错误:

Traceback (most recent call last):
  File "test.py", line 47, in <module>
    check_timezone()
  File "test.py", line 40, in check_timezone
    count = subprocess.check_output(command)
  File "/usr/lib/python2.7/subprocess.py", line 537, in check_output
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 679, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1249, in _execute_child
    raise child_exception-
OSError: [Errno 2] No such file or directory

请帮助我在哪里出错。我是python的新手

5 个答案:

答案 0 :(得分:64)

您必须添加shell=True才能执行shell命令。 check_output正在尝试查找名为date | grep -o -w '"+tz+"'' | wc -w的可执行文件但他无法找到它。 (不知道为什么你从错误信息中删除了基本信息)。

看到之间的区别:

>>> subprocess.check_output('date | grep 1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.4/subprocess.py", line 603, in check_output
    with Popen(*popenargs, stdout=PIPE, **kwargs) as process:
  File "/usr/lib/python3.4/subprocess.py", line 848, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.4/subprocess.py", line 1446, in _execute_child
    raise child_exception_type(errno_num, err_msg)
FileNotFoundError: [Errno 2] No such file or directory: 'date | grep 1'

并且:

>>> subprocess.check_output('date | grep 1', shell=True)
b'gio 19 giu 2014, 14.15.35, CEST\n'

阅读有关Frequently Used Arguments的文档,了解有关shell参数的更多信息,以及它如何更改其他参数的解释。


请注意,您应该尽量避免使用shell=True,因为生成shell可能会带来安全隐患(即使您不执行不受信任的输入攻击,仍然可以执行Shellshock!)。

子流程模块的文档有一些关于replacing the shell pipeline的部分。 您可以通过在python中生成两个进程并使用subprocess.PIPE

来实现
date_proc = subprocess.Popen(['date'], stdout=subprocess.PIPE)
grep_proc = subprocess.check_output(['grep', '1'], stdin=date_proc.stdout, stdout=subprocess.PIPE)
date_proc.stdout.close()
output = grep_proc.communicate()[0]

您可以编写一些简单的包装函数来轻松定义管道:

import subprocess
from shlex import split
from collections import namedtuple
from functools import reduce

proc_output = namedtuple('proc_output', 'stdout stderr')


def pipeline(starter_command, *commands):
    if not commands:
        try:
            starter_command, *commands = starter_command.split('|')
        except AttributeError:
            pass
    starter_command = _parse(starter_command)
    starter = subprocess.Popen(starter_command, stdout=subprocess.PIPE)
    last_proc = reduce(_create_pipe, map(_parse, commands), starter)
    return proc_output(*last_proc.communicate())

def _create_pipe(previous, command):
    proc = subprocess.Popen(command, stdin=previous.stdout, stdout=subprocess.PIPE)
    previous.stdout.close()
    return proc

def _parse(cmd):
    try:
        return split(cmd)
    except Exception:
        return cmd

有了这个,你可以写pipeline('date | grep 1')pipeline('date', 'grep 1')pipeline(['date'], ['grep', '1'])

答案 1 :(得分:3)

根据我的经验,FileNotFound与子进程的最常见原因是在命令中使用了空格。请改用列表。

# Wrong, even with a valid command string
subprocess.run(["date | grep -o -w '\"+tz+\"' | wc -w"])

# Fixed
subprocess.run(["date", "|", "grep", "-o", "-w", "'\"+tz+\"'", "|", "wc", "-w"])

此更改不会导致FileNotFound错误,如果您使用更简单的命令搜索此异常,这是一个很好的解决方案。如果您使用的是python 3.5或更高版本,请尝试使用此方法:

import subprocess

a = subprocess.run(["date"], stdout=subprocess.PIPE)
print(a.stdout.decode('utf-8'))

b = subprocess.run(["grep", "-o", "-w", "'\"+tz+\"'"],
                   input=a.stdout, stdout=subprocess.PIPE)
print(b.stdout.decode('utf-8'))    

c = subprocess.run(["wc", "-w"],
                   input=b.stdout, stdout=subprocess.PIPE)
print(c.stdout.decode('utf-8'))

您应该看到一个命令的输出如何成为另一个输入,就像使用shell管道一样,但是您可以在python中轻松调试该过程的每个步骤。对于python&gt;,建议使用subprocess.run; 3.5,但在以前的版本中不可用。

答案 2 :(得分:2)

发生FileNotFoundError的原因是-在没有shell=True的情况下-Python试图查找其文件名就是您要传入的整个字符串的可执行文件。您需要添加shell=True才能将Shell移至解析并执行字符串,或者弄清楚如何重新构建此命令行以避免使用shell。

这里的shell编程绝对很奇怪。在任何普通系统上,date绝对不会输出"+tz+",因此其余的处理是没有意义的。

此外,使用wc -w来计数来自grep的输出单词数是不寻常的。更为常见的用例(如果不能简单地使用grep -c来计算匹配行的数量)将是使用wc -l来计算grep的输出行。

如果可以,you want to avoid shell=True;如果此处的目的是测试date命令,则可能应该用本机Python代码替换其余的Shell脚本。

优点:

  • 试图了解程序的人只需要了解Python,而无需了解shell脚本。
  • 该脚本将具有较少的外部依赖项(此处为date),而不需要类似Unix的平台。

缺点:

  • 在Python中重新实现标准的Unix工具很累,有时甚至很冗长。

为了避免这种情况,如果只想计算"+tz+"的输出中date出现多长时间,请尝试

p = subprocess.run(['date'],
    capture_output=True, text=True,
    check=True)
result = len(p.stdout.split('"+tz+"'))-1

关键字参数text=True需要Python 3.7;为了与早期Python版本兼容,请尝试使用(误称)旧式同义词universal_newlines=True。对于非常老的Python版本,可能会退回到subprocess.check_output()

如果您确实需要-w的{​​{1}}选项的语义,则需要检查与该匹配项相邻的字符是否不是字母,并排除那些。我将其保留为练习,实际上是假设此处的原始shell脚本实现实际上是不正确的。 (也许尝试grep。)

在更普通的情况下(单个命令,没有管道,通配符,重定向,shell内置等),您可以使用Python的re.split(r'(?<=^|\W)"\+tz\+"(?=\W|$)', p.stdout)将命令解析为正确引用的参数列表。例如,

shlex.split()

请注意,常规字符串>>> import shlex >>> shlex.split(r'''one "two three" four\ five 'six seven' eight"'"nine'"'ten''') ['one', 'two three', 'four five', 'six seven', 'eight\'nine"ten'] 在这里完全不合适;它只是在每个空格字符上分割,不支持任何形式的引号或转义。

答案 3 :(得分:0)

该问题已经在上面给出了答案,但以防万一这些解决方案不适合您;请检查路径本身以及是否为进程设置了所有环境变量以定位路径。

答案 4 :(得分:0)

在 python 3.8.10 上对我有用的东西(受到这里的@mightypile 解决方案的启发:https://stackoverflow.com/a/49986004/12361522),删除了参数拆分,我也必须启用 shell:

这个:

c = subprocess.run(["wc -w"], input=b.stdout, stdout=subprocess.PIPE, shell=True)

代替:

c = subprocess.run(["wc", "-w"], input=b.stdout, stdout=subprocess.PIPE)


如果有人想尝试我的解决方案(至少对于 v3.8.10),这是我的:

我有包含至少 2 种文件类型(.jpg 和其他)的多个文件的目录。我需要通过 1 个管道计算特定文件类型 (.jpg) 而不是目录中的所有文件:

ls *.jpg | wc -l

所以最终我让它像这里一样工作:

import subprocess
proc1 = subprocess.run(["ls *.jpg"], stdout=subprocess.PIPE, shell=True)
proc2 = subprocess.run(['wc -l'], input=proc1.stdout, stdout=subprocess.PIPE, shell=True)
print(proc2.stdout.decode())

它不适用于拆分:

["ls", "*.jpg"] 会使 ls 忽略约束 *.jpg

['wc', '-l'] 将返回正确的计数,但会返回所有 3 个输出,而不仅仅是我所追求的一个

如果没有启用 shell shell=True