Interacting with the operating system is one of the major milestones for anyone learning Python. Imagine automating folder creation, moving files, or configuring servers with just a few lines of code. Knowing how to run terminal commands with Python is not just a productivity trick — it is a fundamental skill for any developer who wants to build robust automation tools integrated with the working environment.
Python provides powerful native libraries that act as a bridge between your script and the system shell (whether the Command Prompt on Windows, Bash on Linux, or Terminal on macOS). This guide focuses on the subprocess module, the official recommendation of the Python Software Foundation to replace older modules like os.system.
Why use Python for terminal commands?
Many CLI tools already do exactly what you need — git, docker, network utilities. Instead of rewriting that logic in pure Python, it makes more sense to call those tools directly. Integrating terminal commands into your Python automation scripts lets you handle errors far more gracefully than a traditional .bat or .sh file. Python’s structure also stays the same across operating systems — you simply detect which system the user is running via the os module and adapt the command dynamically.
The subprocess.run() method
The safest, most modern way to run terminal commands is subprocess.run(), introduced in Python 3.5. It sends the command, waits for it to finish, and captures the result (or error) in an organized way. Pass commands as a list of strings — this avoids “Shell Injection” security issues where malicious users could sneak in extra commands:
import subprocess
# Run 'ls -l' on Linux/Mac or ['dir'] on Windows
subprocess.run(["ls", "-l"])Capturing command output
Use capture_output=True and text=True to save the response in a variable for later processing — for example, reading the machine’s IP or checking an installed software version:
import subprocess
result = subprocess.run(["echo", "Hello from Terminal"], capture_output=True, text=True)
print("Command output:", result.stdout)The text=True parameter converts the bytes received from the system into a readable string, avoiding manual UTF-8 encoding errors. If the command fails, the error is captured in result.stderr.
Error handling with check=True
import subprocess
try:
subprocess.run(["nonexistent_command"], check=True)
except subprocess.CalledProcessError as e:
print(f"Error running command: {e}")
except FileNotFoundError:
print("The requested command was not found on the system.")With check=True, Python automatically raises an exception if the command returns an error code, letting you combine this with try-except to build resilient automation scripts.
Using shell=True for pipes and redirections
For shell-specific features like pipes (|) or redirections (>), pass the command as a single string with shell=True. Use this with caution and never pass raw user input without validation:
import subprocess
# Count files using a pipe (Linux/Mac)
command = "ls | wc -l"
result = subprocess.run(command, shell=True, capture_output=True, text=True)
print(f"Total files: {result.stdout}")subprocess.run vs os.system
os.system is considered legacy for most modern tasks. It cannot easily capture output, handles errors poorly, and is far less flexible. subprocess.run supports output capture, raises proper exceptions, and uses lists for safer command passing — it is the current standard.
Complete project: cross-platform network monitor
import subprocess
import platform
def monitor_system():
system = platform.system()
print(f"Detected OS: {system}")
# Windows uses -n, Unix uses -c
param = "-n" if system == "Windows" else "-c"
command = ["ping", param, "1", "8.8.8.8"]
print("Testing connection to Google DNS (8.8.8.8)...")
try:
result = subprocess.run(command, capture_output=True, text=True, timeout=5)
if result.returncode == 0:
print("Connection active! Details:")
print(result.stdout)
else:
print("Connection failed. Server did not respond.")
except subprocess.TimeoutExpired:
print("Error: Command took too long to respond.")
except Exception as e:
print(f"Unexpected error: {e}")
if __name__ == "__main__":
monitor_system()Frequently Asked Questions
Does subprocess.run() replace os.system()?
Yes. subprocess.run() is more flexible, secure, and recommended by current Python language standards for any execution of external commands.
Why do I get FileNotFoundError?
The command is not in the system’s PATH. Verify the program is installed or provide the full executable path (e.g. /usr/bin/git).
Can I run scripts from other languages with Python?
Yes. subprocess.run(["node", "script.js"]) or subprocess.run(["php", "file.php"]) works the same way as running those commands manually in the terminal.
Is shell=True always dangerous?
No. It is safe when the command is a fixed string defined by you. The risk exists when you concatenate variables that come from user input into the command string.
How do I stop a command that is taking too long?
Use the timeout argument in subprocess.run(). If the time limit is reached, Python raises a TimeoutExpired exception you can handle gracefully.






