80

I need to run the command date | grep -o -w '"+tz+"'' | wc -w using Python on my localhost. I am using subprocess module for the same and using the check_output method as I need to capture the output for the same.

However it is throwing me an error :

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
Seanny123
  • 8,776
  • 13
  • 68
  • 124
h4ck3d
  • 6,134
  • 15
  • 51
  • 74
  • @wnnmaw `subprocess.check_output(command) ` where command is as mentioned in OP – h4ck3d Jun 19 '14 at 12:12
  • 1
    How are you setting the value of `command`? (It's not clear how all the single and double quotes in the OP line up.) What's the value of `tz`? – chepner Jun 19 '14 at 12:47
  • Its a value im getting from optparser. ANyways the issue is resolved. Thanks – h4ck3d Jun 19 '14 at 12:51
  • If the command you are trying to run is a shell built-in, see https://stackoverflow.com/questions/35046004/subprocess-filenotfound – tripleee Oct 15 '20 at 08:40
  • Perhaps see also https://stackoverflow.com/questions/7323859/python-how-to-call-bash-commands-with-pipe – tripleee Feb 07 '21 at 09:23

6 Answers6

147

You have to add shell=True to execute a shell command. check_output is trying to find an executable called: date | grep -o -w '"+tz+"'' | wc -w and he cannot find it. (no idea why you removed the essential information from the error message).

See the difference between:

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

And:

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

Read the documentation about the Frequently Used Arguments for more information about the shell argument and how it changes the interpretation of the other arguments.


Note that you should try to avoid using shell=True since spawning a shell can be a security hazard (even if you do not execute untrusted input attacks like Shellshock can still be performed!).

The documentation for the subprocess module has a little section about replacing the shell pipeline. You can do so by spawning the two processes in python and use 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]

You can write some simple wrapper function to easily define pipelines:

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

With this in place you can write pipeline('date | grep 1') or pipeline('date', 'grep 1') or pipeline(['date'], ['grep', '1'])

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • 2
    Just FYI, certain implementations (possibly versions?) do not raise a "FileNotFoundError", but an "OS Error" with no helpful information, just as he pasted. So that is why he "removed the essential information" – Keozon Nov 22 '16 at 23:50
  • "Warning: Passing shell=True can be a security hazard if combined with untrusted input. See the warning under Frequently Used Arguments for details." - https://docs.python.org/2/library/subprocess.html#popen-constructor – Jivan Dec 27 '17 at 20:33
  • @Jivan You are right. Updated the answer with that note and also a way to easily define pipelines without using `shell=True`. – Bakuriu Dec 28 '17 at 18:10
  • All of this can easily be done natively in Python. Spawning a shell to do something Python can trivially do is just wasteful, as well as somewhat more error prone (more corner cases and opaque behavior). – tripleee Dec 28 '17 at 19:40
  • @tripleee Did you carefully read the question and the answer?. The question is about a `FileNotFoundError`, my answer exaplain why that error happens, a simple way to fix this to do what the OP wants and also a way to do it without spawning shells. – Bakuriu Dec 28 '17 at 19:48
  • Of course, but I feel the answer is incomplete if it fails to point out that a much better solution to this particular problem is to avoid using subprocesses. – tripleee Dec 29 '17 at 06:48
  • @tripleeeFair enough. In any case, consider that for some things using subprocesses is actually better than implementing the logic in pure python. For example: sorting the lines of a file surely can be done in python easily. But if you have a 50G file it will fail with a memory error. Calling `sort` on the file works like a charm since it implements algorithms that do not need to keep all the file in memory to sort it. And re-implementing those algorithms is surely more error-prone than using a working product with decades of widespread usage. – Bakuriu Dec 29 '17 at 12:09
  • Regarding the OSError versus FileNotFoundError, I think FileNotFoundError was added in Python 3.5, before that would have been OSError. – Apollo Data Mar 27 '18 at 23:42
  • 1
    `subprocess.check` got AttributeError: 'module' object has no attribute 'check' – Lei Yang Mar 09 '19 at 02:35
  • @LeiYang That's obviously a typo since the 4 calls before are `check_output`. I fixed it. – Bakuriu Mar 09 '19 at 08:48
21

The most common cause of FileNotFound with subprocess, in my experience, is the use of spaces in your command. If you have just a single command (not a pipeline, and no redirection, wildcards, etc), use a list instead.

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

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

This change results in no more FileNotFound errors, and is a nice solution if you got here searching for that exception with a simpler command.

If you need a pipeline or other shell features, the simple fix is to add shell=True:

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

However, if you are using python 3.5 or greater, try using this approach:

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

You should see how one command's output becomes another's input just like using a shell pipe, but you can easily debug each step of the process in python. Using subprocess.run is recommended for python > 3.5, but not available in prior versions.

tripleee
  • 175,061
  • 34
  • 275
  • 318
mightypile
  • 7,589
  • 3
  • 37
  • 42
  • Had an issue with windows and cygwin ("Is a directory", "Invalid syntax", etc) . Using this resolved my issue as I guess it does not let the shell resolve the path. Lifesaver thank you – StackHola Jul 09 '21 at 12:25
  • this helped me here: https://gis.stackexchange.com/questions/436664/gdal-doesn-t-recognise-ubuntu-file-path-or-shapefile-python-does when trying to run a command via `call` which previously worked on Windows machines and python 3.8. Now on a Linux Ubuntu and python 3.10, your list solution worked a treat. – Theo F Jul 23 '22 at 10:12
8

The FileNotFoundError happens because - in the absence of shell=True - Python tries to find an executable whose file name is the entire string you are passing in. You need to add shell=True to get the shell to parse and execute the string, or figure out how to rearticulate this command line to avoid requiring a shell.

As an aside, the shell programming here is decidedly weird. On any normal system, date will absolutely never output "+tz+" and so the rest of the processing is moot.

Further, using wc -w to count the number of output words from grep is unusual. The much more common use case (if you can't simply use grep -c to count the number of matching lines) would be to use wc -l to count lines of output from grep.

Anyway, if you can, you want to avoid shell=True; if the intent here is to test the date command, you should probably replace the rest of the shell script with native Python code.

Pros:

  • The person trying to understand the program only needs to understand Python, not shell script.
  • The script will have fewer external dependencies (here, date) rather than require a Unix-like platform.

Cons:

  • Reimplementing standard Unix tools in Python is tiresome and sometimes rather verbose.

With that out of the way, if the intent is simply to count how wany times "+tz+" occurs in the output from date, try

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

The keyword argument text=True requires Python 3.7; for compatibility back to earlier Python versions, try the (misnomer) legacy synonym universal_newlines=True. For really old Python versions, maybe fall back to subprocess.check_output().

If you really need the semantics of the -w option of grep, you need to check if the characters adjacent to the match are not alphabetic, and exclude those which are. I'm leaving that as an exercise, and in fact would assume that the original shell script implementation here was not actually correct. (Maybe try re.split(r'(?<=^|\W)"\+tz\+"(?=\W|$)', p.stdout).)

In more trivial cases (single command, no pipes, wildcards, redirection, shell builtins, etc) you can use Python's shlex.split() to parse a command into a correctly quoted list of arguments. For example,

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

Notice how the regular string split() is completely unsuitable here; it simply splits on every whitespace character, and doesn't support any sort of quoting or escaping. (But notice also how shlex.split boneheadedly just returns a list of tokens from the original input:

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

(Even more parenthetically, this isn't exactly the original input, which had a superfluous extra single quote after '"+tz+"').

If you were to pass this to subprocess.run, it is in fact passing | and grep etc as arguments to date, not implementing a shell pipeline! You still have to understand what you are doing.)

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • 1
    See https://stackoverflow.com/a/51950538/874188 which has a section with slightly more detailed example of common shell constructs and how to replace them with Python code. – tripleee Oct 15 '20 at 08:23
0

The question already has an answer above but just in case the solutions are not working for you; Please check the path itself and if all the environment variables are set for the process to locate the path.

Yash Gupta
  • 187
  • 2
  • 11
0

what worked for me on python 3.8.10 (inspired by @mightypile solution here: https://stackoverflow.com/a/49986004/12361522), was removed splits of parametres and i had to enable shell, too:

this:

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

instead of:

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


if anyone wanted to try my solution (at least for v3.8.10), here is mine:

i have directory with multiple files of at least 2 file-types (.jpg and others). i needed to count specific file type (.jpg) and not all files in the directory, via 1 pipe:

ls *.jpg | wc -l

so eventually i got it working like here:

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

it would not work with splits:

["ls", "*.jpg"] that would make ls to ignore contraint *.jpg

['wc', '-l'] that would return correct count, but will all 3 outputs and not just one i was after

all that would not work without enabled shell shell=True

  • 1
    This is very confused. Generally you should use _either_ a list as the first argument, _or_ `shell=True`. See also [Actual meaning of `shell=True` in subprocess](https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess).You don't need the shell for this at all, though; `print(len(glob.glob("*.jpg")))` – tripleee Jun 06 '22 at 04:40
  • Because of [security reasons it is not recommended](https://docs.python.org/3/library/subprocess.html#security-considerations) to use `shell=True`. – buhtz Nov 30 '22 at 09:34
0

I had this error too and what worked for me was setting the line endings of the .sh file - that I was calling with subprocess - to Unix (LF) instead of Windows CRLF.

joliver
  • 131
  • 1
  • 8