HOWTO · Python

How to Run Bash Scripts in Python

Learn how to run Bash scripts from Python with subprocess.run, os.system, and subprocess.Popen. Choose a method, pass arguments, capture output, and provide input to a script.

On this page

Use subprocess.run for most Bash scripts that should finish before Python continues. It lets you pass arguments separately, capture stdout and stderr, and check the process’s returncode. Bash must be installed and available to Python, so this approach depends on the host environment and is not portable to systems without Bash. The example below creates its own script file, passes a name as an argument, and displays the captured result.

Method 1: Using the subprocess Module

This self-contained example creates a small Bash script, passes it an argument, and captures both output streams. The Bash script prints a greeting; Python then prints the captured text and the process status.

import subprocess
import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    script_path = Path(directory) / "greet.sh"
    script_path.write_text(
        '#!/usr/bin/env bash\nprintf "Hello, %s!\\n" "$1"\n',
        encoding="utf-8",
    )

    result = subprocess.run(
        ["bash", str(script_path), "Ada"],
        capture_output=True,
        text=True,
        check=False,
    )

print(result.stdout, end="")
print(f"Return code: {result.returncode}")
Hello, Ada!
Return code: 0

The argument list keeps the script path and Ada as separate arguments. With the default shell=False, Python starts Bash directly rather than asking a shell to parse the list. This avoids shell parsing for those arguments, but it is not a universal safety guarantee: validate inputs and consider what the script itself does. A zero returncode indicates success; a nonzero value indicates that the process reported failure. stderr is a separate stream for diagnostics, and text appearing there does not by itself mean the command failed. capture_output=True makes both stdout and stderr available on the result object.

Method 2: Using os.system

Use os.system only when a simple command is enough and Python does not need to capture its output. It accepts one command string, runs it through a system shell, and sends the command’s streams to the surrounding process’s standard streams. Its return value is system-dependent and may encode process status rather than equal the child process’s exit code, so do not treat it as a portable direct exit-code value. It offers no direct stdout or stderr result object. Avoid building its command string from untrusted input because shell parsing can give that input special meaning. For separate arguments, captured streams, and reliable status inspection, prefer subprocess.

Method 3: Using subprocess.Popen

Choose subprocess.Popen when you need lower-level control over a running process, such as managing its lifetime or connecting pipes. For a finite exchange, set stdin=subprocess.PIPE when creating the process, then call communicate(input=...) to send input and collect captured streams after the process finishes. Without that stdin pipe, communicate(input=...) cannot send data to the child. It does not deliver output in real time. If you create pipes for both output streams, drain them with communicate(); waiting on a child while its pipe fills can deadlock. For live streaming, read and drain the streams deliberately instead of treating communicate() as a streaming API. The exact pipe behavior depends on the platform and process.

Conclusion

For a Bash script that runs to completion, subprocess.run is usually the clearest choice: pass a list, capture the streams when needed, and use returncode to determine success. Reserve os.system for simple shell commands without output handling, and use subprocess.Popen when explicit process control or communication is required. Check that Bash is installed and available in the environment where the Python program runs; a script can only be started if Python can find the Bash executable.

FAQ

  1. What is the best way to run a Bash script from Python?
    Use subprocess.run for most scripts that finish before Python needs to continue. It supports argument lists, captured streams, and a process status through returncode.

  2. How do I capture a script’s output?
    Set capture_output=True and text=True in subprocess.run, then read stdout and stderr from the returned object. With Popen, connect the needed pipes and call communicate() to collect streams after completion; this collects output rather than streaming it live. stderr can contain diagnostics even when the process succeeds, so use returncode to check its status.

  3. Is passing a list safe?
    With shell=False, a list avoids shell parsing of the supplied arguments. This reduces shell-injection risk, but does not make every script or input safe; validate values and consider the behavior implemented by the script.

  4. Can I pass arguments to a Bash script?
    Yes. Add each argument as a separate item after the script path in the subprocess argument list. os.system takes a single command string, so it does not provide the same structured argument passing.

  5. How can I provide input to the script?
    Use subprocess.run with input=... for a simple finite exchange, or use Popen with stdin=subprocess.PIPE and communicate(input=...) when you need lower-level process control. communicate() sends the input and collects the process streams after the child finishes.