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

Time:03-03

If I run command from /bin/bash or /bin/sh, I get a similar result:

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 codes are 0 each time).

If I run it from python, it looks like I need to use shell=True, though I do not understand why:

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'

Most suprisingly it raises a FileNotFoundError, instead of a subprocess.CalledProcessError.

Adding either or both of text=True and check=True does not change anything.

Using something else as command does work as expected (either a CompletedProcess with a returncode of 0, or a subprocess.CalledProcessError). I've tried with printf, echo, ls, cat, which...

So, what is so special with command?

CodePudding user response:

command is a built-in keyword for bash and other shells. Most likely, your system has no actual /usr/bin/command executable in its search PATH.

subprocess.run('command -v lualatex', shell=True) is roughly equivalent to subprocess.run(['bash', '-c', 'command -v lualatex']) on systems with the bash shell as default. Then bash will interpret command as its built-in version instead of the system executable version.

subprocess.run(['command', '-v', 'lualatex']) on the other hand tries to directly call the system's command executable, which isn't there, so FileNotFoundError. The actual sub-process was never spawned at all in this case, since there's no executable to call.

From comments below, it appears that you may need to use shell=True to use command on your system. This is generally considered unsafe for multiple reasons. However, you can mitigate some of the risks (e.g. of basic injection) by properly escaping the arguments you send in with shlex.join. The shlex.join function converts a list of arguments (first one is command name) to a string that's safe for use with shell=True; it ensures that the right escaping and quoting is done:

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