Search code examples
pythonmacossubprocesstouch

Use python subprocess Popen to touch a file


I am new to the subprocess module, and wondering why the first subprocess failed while the second one worked. I am on py3.7 and macOS.

>>> from subprocess import PIPE, Popen, STDOUT
>>> Popen(['touch', '/Users/me/fail.txt'], stdout=PIPE, stderr=STDOUT, shell=True)
>>> Popen(['touch /Users/me/ok.txt'], stdout=PIPE, stderr=STDOUT, shell=True)

Solution

  • According to the docs:

    The shell argument (which defaults to False) specifies whether to use the shell as the program to execute. If shell is True, it is recommended to pass args as a string rather than as a sequence.

    On POSIX with shell=True, the shell defaults to /bin/sh. If args is a string, the string specifies the command to execute through the shell. This means that the string must be formatted exactly as it would be when typed at the shell prompt. This includes, for example, quoting or backslash escaping filenames with spaces in them. If args is a sequence, the first item specifies the command string, and any additional items will be treated as additional arguments to the shell itself. That is to say, Popen does the equivalent of:

    Popen(['/bin/sh', '-c', args[0], args[1], ...])
    

    So in the first case the 2nd element of the list is passed as an argument to /bin/sh itself, not the touch command. So you are basically running:

    user@name ~$ touch
    

    Which produces the following error:

    touch: missing file operand
    Try 'touch --help' for more information.
    

    And if you read the stdout of your first command, you will find the same:

    >>> Popen(['touch', '/Users/me/fail.txt'], stdout=PIPE, stderr=STDOUT, shell=True).stdout.read()
    b"touch: missing file operand\nTry 'touch --help' for more information.\n"
    

    So while shell=True, it is better to pass string.