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

Posted

技术标签:

【中文标题】启动包含管道命令的子进程时找不到文件错误【英文标题】:File not found error when launching a subprocess containing piped commands 【发布时间】:2014-08-09 23:14:13 【问题描述】:

我需要在本地主机上使用 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

【问题讨论】:

@wnnmaw subprocess.check_output(command) 其中命令如 OP 中所述 你是如何设置command的值的? (不清楚 OP 中的所有单引号和双引号是如何排列的。)tz 的值是多少? 这是我从 optparser 获得的值。无论如何问题都解决了。谢谢 如果您尝试运行的命令是内置的shell,请参阅***.com/questions/35046004/subprocess-filenotfound 或许也可以看看***.com/questions/7323859/… 【参考方案1】:

您必须添加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'])

【讨论】:

仅供参考,某些实现(可能是版本?)不会引发“FileNotFoundError”,而是会引发“OS 错误”而没有有用的信息,就像他粘贴的那样。所以这就是他“删除基本信息”的原因 “警告:如果与不受信任的输入结合使用,传递 shell=True 可能会造成安全隐患。有关详细信息,请参阅常用参数下的警告。” - docs.python.org/2/library/subprocess.html#popen-constructor @Jivan 你是对的。使用该注释更新了答案,也是一种无需使用shell=True 即可轻松定义管道的方法。 所有这些都可以在 Python 中轻松完成。生成一个 shell 来做一些 Python 可以做的事情只是浪费,而且更容易出错(更多的极端情况和不透明的行为)。 subprocess.check got AttributeError: 'module' object has no attribute 'check'【参考方案2】:

根据我的经验,FileNotFound with subprocess 最常见的原因是在您的命令中使用了空格。如果您只有一个命令(不是管道,也没有重定向、通配符等),请改用列表。

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

# Fixed; notice also 
subprocess.run(["grep", "-o", "-w", '"+tz+"'])

此更改不再导致 FileNotFound 错误,如果您在这里使用更简单的命令搜索该异常,这是一个很好的解决方案。

如果您需要管道或其他 shell 功能,简单的解决方法是添加shell=True

subprocess.run(
    '''date | grep -o -w '"+tz+"'' | wc -w''',
    shell=True)

但是,如果您使用的是 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 > 3.5,建议使用subprocess.run,但在以前的版本中不可用。

【讨论】:

遇到了 windows 和 cygwin 的问题(“是目录”、“无效语法”等)。使用它解决了我的问题,因为我猜它不会让 shell 解析路径。谢谢你的救星【参考方案3】:

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()

如果你真的需要grep-w选项的语义,你需要检查匹配相邻的字符是否不是字母的,并排除那些。我把它作为一个练习,实际上会假设这里的原始 shell 脚本实现实际上并不正确。 (也许试试re.split(r'(?&lt;=^|\W)"\+tz\+"(?=\W|$)', p.stdout)。)

在更简单的情况下(单个命令、无管道、通配符、重定向、shell 内置函数等),您可以使用 Python 的 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']

注意常规字符串split() 在这里完全不合适;它只是在每个空白字符上拆分,并且不支持任何类型的引用或转义。 (但也要注意它是如何愚蠢地从原始输入中返回一个标记列表:

>>> shlex.split('''date | grep -o -w '"+tz+"' | wc -w''')
['date', '|', 'grep', '-o', '-w', '"+tz+"', '|', 'wc', '-w']

(更重要的是,这不完全是原始输入,它在'"+tz+"' 之后有一个多余的额外单引号)。

这实际上是将|grep 等作为参数传递给date,而不是实现shell 管道!您仍然必须了解自己在做什么。)

【讨论】:

请参阅***.com/a/51950538/874188,其中有一个部分包含更详细的常见 shell 构造示例以及如何用 Python 代码替换它们。【参考方案4】:

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

【讨论】:

【参考方案5】:

在 python 3.8.10 上对我有用的东西(受@mightypile 解决方案的启发:https://***.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,所有这些都将无法工作

【讨论】:

以上是关于启动包含管道命令的子进程时找不到文件错误的主要内容,如果未能解决你的问题,请参考以下文章

使用带有 python3 的子进程模块管道两个命令时遇到问题

UNIX中C语言的管道

带有管道的子进程调用[重复]

《拉钩课程

进程间的通信——pipe通信

错误:使用节点命令运行 JS 文件时找不到模块“firebase”