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

vybvopom  于 2022-11-30  发布在  Shell
关注(0)|答案(6)|浏览(230)

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

r55awzrz1#

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

有关shell参数及其如何更改其他参数解释的详细信息,请阅读有关“常用参数”的文档。
请注意,您应该尽量避免使用shell=True,因为生成shell可能会带来安全隐患(即使您不执行Shellshock之类的不受信任的输入攻击,也可能会被执行!)。
subprocess模块的文档中有一小部分是关于替换shell管道的,你可以通过在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]

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

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'])

nzrxty8p

nzrxty8p2#

根据我的经验,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=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中过程的每一步。subprocess.run对于python 3.5以上的版本,建议使用www.example.com,但在以前的版本中不可用。

oug3syen

oug3syen3#

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'(?<=^|\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']

(Even顺便说一句,这并不是原始输入,它在'"+tz+"'后面有一个多余的单引号)。
这实际上是将|grep等作为参数传递给date,而不是实现shell管道!您仍然需要了解您在做什么。)

t5zmwmid

t5zmwmid4#

上面的问题已经有了答案,但只是以防万一解决方案对您不起作用;请检查路径本身,并检查是否为进程设置了所有环境变量以查找路径。

i86rm4rw

i86rm4rw5#

我在Python3.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和其他)的多个文件的目录。我需要计数特定的文件类型(. jpg),而不是目录中的所有文件,通过1管道:

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个输出,而不仅仅是一个i
如果没有启用的shell shell=True,所有这些都将无法工作

8wigbo56

8wigbo566#

我也遇到了这个错误,对我有效的是将.sh文件的行尾设置为Unix (LF),而不是Windows CRLF

相关问题