subprocess.run failing to execute git command with var=value prefix but os.system does it without issue

Question:

I’m running into a bit of a weird issue here.

I’m trying to clone some GitHub repositories automatically but I’m seeing subprocess.run error out in cases where os.system, or just running the command directly in my shell, works fine.

This is the command I’m trying to run:

subprocess.run('GIT_TERMINAL_PROMPT=0 git clone https://github.com/fake-user/fake-repo.git'.split())

Which leads to this error:

>>> subprocess.run('GIT_TERMINAL_PROMPT=0 git clone https://github.com/fake-user/fake-repo.git'.split())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/omermikhailk/.pyenv/versions/3.11.0/lib/python3.11/subprocess.py", line 546, in run
    with Popen(*popenargs, **kwargs) as process:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/omermikhailk/.pyenv/versions/3.11.0/lib/python3.11/subprocess.py", line 1022, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/Users/omermikhailk/.pyenv/versions/3.11.0/lib/python3.11/subprocess.py", line 1899, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'GIT_TERMINAL_PROMPT=0'

When the result should be what os.system gives:

>>> os.system('GIT_TERMINAL_PROMPT=0 git clone https://github.com/fake-user/fake-repo.git')
Cloning into 'fake-repo'...
fatal: could not read Username for 'https://github.com': terminal prompts disabled
32768

Would anyone happen to know the reason behind this?

Asked By: omermikhailk

||

Answers:

You need to use the following instead:

subprocess.run(['git', 'clone', 'https://github.com/fake-user/fake-repo.git'], env={'GIT_TERMINAL_PROMPT': '0'})

When you run a command via subprocess, you are not using bash or sh or any shell, so environment variables are not interpreted, and must be specified via env, not the CLI.

Note that it is the best practice to explicitly craft the list for the command and its arguments yourself, rather than using a string and .split(). If you have any arguments that contain spaces, rather than including literal quotes in the string that you pass as a list element, you would use a string that contains the full argument. I.e., NOT ['echo', '"foo', 'bar"'] (which would be the result of .split()), but ['echo', 'foo bar'] to include the space.

Another note as to why this occurs is that subprocess.run does technically allow you to pass shell=True, but you should avoid doing so at all costs due to security implications if you are working with any content that may be user-generated. The best practice is to use env={...} as suggested.

If you need to include current environment variables, then you can use the following:

from os import environ

env = environ.copy()
env['GIT_TERMINAL_PROMPT'] = '0'

subprocess.run(['git', 'clone', 'https://github.com/fake-user/fake-repo.git'], env=env)
Answered By: dskrypa