为什么在 subprocess.run() 中调用 `command -v` 会引发 FileNotFoundError?

Why does calling `command -v` in subprocess.run() raise a FileNotFoundError?

如果我从 /bin/bash/bin/sh 运行 command,我得到类似的结果:

nico@xantico:~$ command -v lualatex
/usr/local/texlive/2021/bin/x86_64-linux/lualatex
nico@xantico:~$ sh
$ command -v lualatex
/usr/local/texlive/2021/bin/x86_64-linux/lualatex
$

(return码每次都是0).

如果我从 python 运行,看起来我需要使用 shell=True,虽然我不明白为什么:

nico@xantico:~$ python
Python 3.8.10 (default, Nov 26 2021, 20:14:08) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess

>>> subprocess.run('command -v lualatex', shell=True)
/usr/local/texlive/2021/bin/x86_64-linux/lualatex
CompletedProcess(args='command -v lualatex', returncode=0)

>>> subprocess.run('command -v lualatex'.split())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/subprocess.py", line 493, in run
    with Popen(*popenargs, **kwargs) as process:
  File "/usr/lib/python3.8/subprocess.py", line 858, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.8/subprocess.py", line 1704, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'command'

最令人惊讶的是它引发了 FileNotFoundError,而不是 subprocess.CalledProcessError

添加 text=Truecheck=True 之一或两者都不会改变任何东西。

command 一样使用其他东西确实可以按预期工作(CompletedProcess0returncodesubprocess.CalledProcessError)。我试过 printfecholscatwhich...

那么,command 有什么特别之处?

commandbash 和其他 shell 的 built-in 关键字。很可能,您的系统在其搜索 PATH.

中没有实际的 /usr/bin/command 可执行文件

subprocess.run('command -v lualatex', shell=True) 大致等同于默认为 bash shell 的系统上的 subprocess.run(['bash', '-c', 'command -v lualatex'])。然后 bash 会将 command 解释为它的 built-in 版本而不是系统可执行版本。

另一方面,

subprocess.run(['command', '-v', 'lualatex']) 试图直接调用系统的 command 可执行文件,但它不存在,因此 FileNotFoundError。在这种情况下,实际的 sub-process 根本没有生成,因为没有可执行文件可以调用。

从下面的评论来看,您可能需要使用 shell=True 才能在您的系统上使用 command。由于多种原因,这通常被认为是不安全的。但是,您可以通过适当地转义使用 shlex.join 发送的参数来减轻 一些 的风险(例如基本注入)。 shlex.join 函数将参数列表(第一个是命令名称)转换为可安全用于 shell=True 的字符串;它确保完成正确的转义和引用:

import subprocess, shlex
subprocess.run(shlex.join(['command', '-v', 'lualatex']), shell=True)