subprocess.run with the command as a list: the program first, then each argument as its own item.
import subprocess result = subprocess.run( ["git", "log", "--oneline", "-3"], capture_output=True, # keep the output instead of printing it text=True, # give me str, not bytes check=True, # raise if the command fails ) print(result.stdout)
This runs on your own machine only; a browser tab has no shell to run commands in. If commands and shells are new to you, the terminal lesson explains them first.
What each keyword buys you:
capture_output=Trueputs what the command printed intoresult.stdoutandresult.stderr. Without it, the output goes straight to your terminal and your code never sees it.text=Truedecodes the output to a string. Without it you get bytes likeb'a1b2c3 Fix typo\n'.check=TrueraisesCalledProcessErrorwhen the command exits with an error. Without it, a failed command looks exactly like a successful one unless you checkresult.returncodeyourself.
Handling a failure
import subprocess try: subprocess.run(["git", "push"], capture_output=True, text=True, check=True, timeout=60) except subprocess.CalledProcessError as e: print("git push failed with code", e.returncode) print(e.stderr) except FileNotFoundError: print("git isn't installed, or isn't on PATH")
timeout=60 stops a command that hangs waiting for a password prompt you'll never see.
Why a list, and not one string with shell=True
With shell=True, the string goes to the shell, and the shell treats ;, && and | as instructions. If any part of the string came from a user, they can add their own command:
import subprocess filename = input("File to count lines in: ") # someone types: notes.txt; rm -rf ~ # DANGEROUS: the shell runs both commands subprocess.run(f"wc -l {filename}", shell=True) # SAFE: the whole input is one argument, a strange file name and nothing more subprocess.run(["wc", "-l", filename])
This is command injection, and the list form prevents it completely. If you're tempted by shell=True for a pipe or a wildcard, do that part in Python instead: pathlib.Path.glob() for wildcards, and read result.stdout rather than piping to grep.
You'll see os.system() in old answers. It always goes through the shell, can't capture output, and only tells you the exit code. subprocess.run replaced it.