Running Commands with subprocess

The subprocess module lets your Python program launch external commands, capture their output and error text, and check whether they succeeded.

Learn Running Commands with subprocess in our free Python course — an interactive lesson with runnable examples, a practice exercise and a quick reference.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

It is how you call git , run a build tool, convert a file, or glue Python to any command-line program — safely, with full control over input, output, and failures.

The one function you need is subprocess.run . Pass the command as a list of strings, add capture_output=True to grab its output, and text=True so you get strings instead of raw bytes:

The returned object is a CompletedProcess . Its three useful attributes are .returncode (the exit status), .stdout (normal output), and .stderr (error output). You can run Python itself this way using sys.executable , which points at the current interpreter:

A non-zero return code means the command failed. You can inspect .returncode yourself, or let check=True raise a CalledProcessError automatically — the same "fail loudly" idea as an exception:

Use check=True when a failed command should stop your program — it spares you from forgetting to test .returncode by hand. Catch CalledProcessError if you want to handle the failure gracefully.

Passing a list is the safe default: each element is one literal argument, so nothing in the data can be reinterpreted as a command. Passing a string with shell=True hands the whole line to a shell, which is where injection attacks happen:

And a hung command should never freeze your whole program. Pass timeout (in seconds) and catch TimeoutExpired :

Complete the call so it captures output as text and reads the result. Replace each ___ and match the expected output.

First blank: text — text=True returns strings instead of bytes.

Second blank: returncode — a value of 0 means the command succeeded.

❌ Passing the whole command as one string (no shell)

✅ Split it into a list: subprocess.run(["echo", "hello"]) .

❌ Forgetting capture_output, then reading stdout

✅ Add capture_output=True, text=True to collect the output as a string.

✅ Pass a list: subprocess.run(["ls", name]) — the input can never become a command.

Write a helper that runs a snippet of Python and reports whether it succeeded, returning the trimmed output on success and the error text on failure.

Go deeper with the official Python documentation:

Lesson complete — Python can drive other programs now!

You can run commands with subprocess.run , capture stdout and stderr as text, inspect returncode , fail loudly with check=True , avoid the shell=True injection risk by passing a list, and guard against hangs with timeout .

🚀 Up next: Hashing with hashlib — turn data into fixed-size fingerprints.

Practice quiz

What is the modern, recommended function for running external commands?

  • os.system
  • os.popen
  • subprocess.run
  • subprocess.fork

Answer: subprocess.run. subprocess.run is the one safe, flexible function that replaces the older os.system and os.popen.

Which argument makes subprocess.run capture a command's output?

  • capture_output=True
  • stdout=True
  • save=True
  • output=True

Answer: capture_output=True. Pass capture_output=True to collect stdout and stderr on the returned CompletedProcess.

What does text=True do?

  • Runs the command in a text editor
  • Enables colored output
  • Forces UTF-8 only
  • Returns stdout/stderr as strings instead of raw bytes

Answer: Returns stdout/stderr as strings instead of raw bytes. Without text=True, .stdout and .stderr come back as bytes; text=True decodes them to strings.

What does a returncode of 0 mean?

  • The command failed
  • The command succeeded
  • The command is still running
  • No output was produced

Answer: The command succeeded. A returncode of 0 indicates success; any non-zero value means the command failed.

What does check=True do when a command fails?

  • Raises a CalledProcessError automatically
  • Silently retries the command
  • Returns None
  • Prints the error and continues

Answer: Raises a CalledProcessError automatically. check=True turns a non-zero exit into a CalledProcessError, the 'fail loudly' equivalent of an exception.

Why is passing a list of arguments safer than shell=True?

  • Lists run faster
  • shell=True is deprecated
  • Each element is one literal argument, so input can't be reinterpreted as a command
  • Lists support more programs

Answer: Each element is one literal argument, so input can't be reinterpreted as a command. With a list, each item is a literal argument; shell=True parses a string through a shell, enabling command injection.

Which CompletedProcess attribute holds a program's normal output?

  • .stderr
  • .stdout
  • .output
  • .returncode

Answer: .stdout. .stdout holds the standard output, .stderr the error output, and .returncode the exit status.

How do you stop a command that hangs forever?

  • Pass kill=True
  • Call subprocess.stop()
  • Set returncode=1
  • Pass a timeout argument in seconds

Answer: Pass a timeout argument in seconds. Pass timeout=seconds; if the command isn't done by then, Python kills it and raises TimeoutExpired.

What exception is raised when a timeout expires?

  • subprocess.CalledProcessError
  • subprocess.TimeoutExpired
  • TimeoutError only
  • OSError

Answer: subprocess.TimeoutExpired. An expired timeout raises subprocess.TimeoutExpired, which you can catch to handle the stuck process.

What does subprocess.run("echo hello") (a bare string, no shell) do?

  • Prints 'hello' correctly
  • Runs echo with one argument
  • Looks for a program literally named 'echo hello' and raises FileNotFoundError
  • Uses the shell automatically

Answer: Looks for a program literally named 'echo hello' and raises FileNotFoundError. Without shell=True, the whole string is treated as one program name. Split it into a list: ['echo', 'hello'].