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
-
What is the best way to run a Bash script from Python?
Usesubprocess.runfor most scripts that finish before Python needs to continue. It supports argument lists, captured streams, and a process status throughreturncode. -
How do I capture a script’s output?
Setcapture_output=Trueandtext=Trueinsubprocess.run, then readstdoutandstderrfrom the returned object. WithPopen, connect the needed pipes and callcommunicate()to collect streams after completion; this collects output rather than streaming it live.stderrcan contain diagnostics even when the process succeeds, so usereturncodeto check its status. -
Is passing a list safe?
Withshell=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. -
Can I pass arguments to a Bash script?
Yes. Add each argument as a separate item after the script path in thesubprocessargument list.os.systemtakes a single command string, so it does not provide the same structured argument passing. -
How can I provide input to the script?
Usesubprocess.runwithinput=...for a simple finite exchange, or usePopenwithstdin=subprocess.PIPEandcommunicate(input=...)when you need lower-level process control.communicate()sends the input and collects the process streams after the child finishes.