惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
小众软件
小众软件
C
Check Point Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Visual Studio Blog
Last Week in AI
Last Week in AI
P
Proofpoint News Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The GitHub Blog
The GitHub Blog
T
Tailwind CSS Blog
Recorded Future
Recorded Future
雷峰网
雷峰网
WordPress大学
WordPress大学
A
Arctic Wolf
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Project Zero
Project Zero
T
Tor Project blog
T
Threat Research - Cisco Blogs
Scott Helme
Scott Helme
Spread Privacy
Spread Privacy
G
Google Developers Blog
Security Latest
Security Latest
MongoDB | Blog
MongoDB | Blog
T
Threatpost
I
InfoQ
T
Tenable Blog
T
The Exploit Database - CXSecurity.com
S
Security Affairs
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理
C
Cisco Blogs
博客园 - 三生石上(FineUI控件)
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
C
CXSECURITY Database RSS Feed - CXSecurity.com
美团技术团队
Google DeepMind News
Google DeepMind News
The Hacker News
The Hacker News
D
Docker
博客园 - 【当耐特】
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Vercel News
Vercel News
PCI Perspectives
PCI Perspectives
Microsoft Azure Blog
Microsoft Azure Blog
C
CERT Recently Published Vulnerability Notes
月光博客
月光博客
Cloudbric
Cloudbric
A
About on SuperTechFans
F
Fortinet All Blogs

DigitalOcean Community Tutorials

Mastering grep with Regular Expressions for Efficient Text Search It's Time to Break Up with Your Cloud: Why AI Teams are Switching We Built a Private-Document AI App to Test Platform Security. Here Is What We Could Actually Verify. PostgreSQL Explained: A Complete Beginner-to-Advanced Guide How To Install and Configure Postfix on Ubuntu How To Build a Web Application Using Flask in Python 3 Build AI Reading List with DigitalOcean Functions and Mistral How To Concatenate Strings in Python How to Allow MySQL Remote Access Securely How To Install and Use Docker on Rocky Linux How To Build a Multi-Agent AI System with Docker Agent DSPy Use Cases: Build Optimized LLM Pipelines How To Submit AJAX Forms with jQuery Build an AI-Powered GPU Fleet Optimizer with the DigitalOcean AI Platform ADK Monitor GPU Utilization in Real Time: A Complete Guide Reduce File Size of Images in Linux - CLI and GUI methods Reduce PDF File Size in Linux: Tools and Methods How To Set Up a Private Docker Registry on Ubuntu How To Troubleshoot Terraform: Errors and Fixes How to Use Go Modules Python Multiprocessing Example: Process, Pool & Queue Convert Class Components to Functional Components with React Hooks How To Install and Configure Ansible on Ubuntu LLM Tokenizers Simplified: BPE, SentencePiece, and More How To Monitor System Authentication Logs on Ubuntu How to Use Traceroute and MTR to Diagnose Network Issues How to Deploy Postgres to Kubernetes Cluster Importing Packages in Go: A Complete Guide Create RAID Arrays with mdadm on Ubuntu How To Make an HTTP Server in Go How To Set Up Time Synchronization on Ubuntu How To Use Struct Tags in Go apt-key Deprecation: Add Repositories with GPG on Ubuntu Linux ps Command: 20 Real-World Examples Python struct.pack and struct.unpack for Binary Data Deadlock in Java: Examples, Detection, and Prevention How To Use Find and Locate to Search for Files on Linux Structured Resume Skill Extraction Using Mistral-7B Inference How to Use the Python Main Function How to Set Up NemoClaw on a DigitalOcean Droplet with 1-Click Build an End-to-End RAG Pipeline for LLM Applications From Single to Multi-Agent Systems: Key Infrastructure Needs Back Up Data to Object Storage Using Restic How to Generate Videos with LTX-2.3 on DigitalOcean GPU Droplets How To Install LAMP Stack (Apache, MySQL, PHP) on Ubuntu How to Download Files with cURL How To Use Variadic Functions in Go Generate UUIDs with uuidgen on Linux How To Use EJS to Template Your Node Application How to Install Node.js on Ubuntu (Step-by-Step Guide) MongoDB Indexes: Improve Query Performance with Node.js LLM Tool Calling with DigitalOcean AI Platform and Databases What are Text Diffusion Models? - An Overview Crafting a Game from Scratch with GPT-5.4 Building Long-Term Memory in AI Agents with LangGraph and Mem0 How To Install PHP 7.4 and Set Up a Local Development Environment on Ubuntu 20.04 Build a GraphQL API in Go to Upload Files to Spaces How To Lint and Format Code with ESLint in Visual Studio Code Train YOLO26 for Retail Object Detection on DigitalOcean GPUs How To Work with JSON in MySQL How to Use the JavaScript .map() Method Building a Scalable App with MongoDB Using DigitalOcean's MCP Server How to Create an SSH Key in Linux: Easy Step-by-Step Guide Measure MySQL Query Performance with mysqlslap How To Use *args and **kwargs in Python 3 Nemotron 3 helped me find the perfect dish rack? A2A vs MCP - How These AI Agent Protocols Actually Differ How To Install and Manage Supervisor Docker Container Images with Watchtower on Ubuntu Getting Started with Qwen3.5 Vision-Language Models How To Create a New Sudo-Enabled User on Ubuntu How to Use Ansible to Install and Set Up Docker on Ubuntu How To Enable Remote Desktop Protocol Using xrdp on Ubuntu 22.04 How To Convert a String to a List in Python How To Check If a String Contains Another String in Python How to Read a Properties File in Python Python Command Line Arguments: sys.argv, argparse, getopt Mastering Grep command in Linux/Unix: A Beginner's Tutorial Understanding Python Data Types How to Implement a Stack in C With Code Examples How To Install and Use Docker Compose on Ubuntu How to Add and Delete Users on Ubuntu How To Order Query Results in Laravel Eloquent How To Define and Use Handlers in Ansible Playbooks How To Install and Use SQLite on Ubuntu How To Install and Use Homebrew on macOS How To Manage DateTime with Carbon in Laravel and PHP How To Install Git on Ubuntu How To Install and Secure Redis on Ubuntu How To Build and Install Go Programs on Linux Using ldflags to Set Version Information for Go Applications How To Build a Node.js Application with Docker How To Add JavaScript to HTML How To Reset Your MySQL or MariaDB Root Password How To Add Images in Markdown How To Set Up a Production Elasticsearch Cluster with Ansible How To Set Up a Firewall Using firewalld on CentOS Understanding Systemd Units and Unit Files How To Set Up Replication in MySQL How To Use the .htaccess File
Python os.system() vs subprocess: Run System Commands
Anish Singh Walia · 2022-08-04 · via DigitalOcean Community Tutorials

Introduction

Python provides several ways to run system commands from within your code. Whether you need to list files, call external tools, or automate shell workflows, the standard library includes modules that handle all of these scenarios. The two primary approaches are the older os.system() function and the modern subprocess module, which offers subprocess.run(), subprocess.call(), subprocess.Popen(), and related utilities.

This tutorial walks through each method with practical examples, explains when to use each one, and covers important topics like output capture, error handling, return codes, and security risks associated with shell=True. By the end, you will know which approach fits your use case and how to run external commands safely from Python.

Key takeaways

  • subprocess.run() is the recommended way to execute system commands in Python 3.5 and later, replacing both os.system() and subprocess.call().
  • Avoid shell=True unless you specifically need shell features like pipes or wildcards. Passing a list of arguments without the shell prevents command injection vulnerabilities.
  • Use capture_output=True with subprocess.run() to capture stdout and stderr as strings or bytes. This gives you full control over command output.
  • Set check=True to automatically raise a CalledProcessError when a command returns a non-zero exit code, making error handling straightforward.
  • For advanced scenarios like long-running processes or real-time output streaming, use subprocess.Popen with .communicate(), .poll(), or .wait().

What are Python system commands?

A Python system command is any call from Python code that asks the operating system to execute an external program or shell instruction. This includes running utilities like ls, git, curl, or any other command-line tool installed on the machine.

Python supports this through several built-in functions and modules:

  • os.system() passes a command string to the system shell. It is the simplest approach but provides no access to the command’s output.
  • subprocess.call() runs a command and returns its exit code. It is part of the subprocess module and is considered a step up from os.system().
  • subprocess.run() is the modern, recommended function (introduced in Python 3.5). It returns a CompletedProcess object with the return code, captured output, and error streams.
  • subprocess.Popen() provides the most control. It lets you interact with a running process in real time, stream output line by line, and manage stdin/stdout/stderr pipes directly.

The rest of this tutorial covers each of these methods in detail with working code examples.

Using os.system() to run shell commands

The os.system() function executes a command string through the system shell and returns the exit status of the command. It is the most basic way to run an external command from Python.

Here is a simple example that checks the installed Python version:

import os

cmd = "python3 --version"

returned_value = os.system(cmd)
print("returned value:", returned_value)

Output:

Python 3.14.3
returned value: 0

A return value of 0 means the command ran successfully. Any non-zero value signals an error.

There are a few things to keep in mind with os.system():

  • No output capture. The command’s output goes directly to the console. You cannot store it in a variable.
  • Limited error handling. You only get the exit code. There is no way to capture stderr separately.
  • Shell execution. The command string is passed directly to the shell (/bin/sh on Unix, cmd.exe on Windows), which introduces potential security risks if any part of the command comes from user input.

The Python os module documentation itself recommends using the subprocess module instead. The os.system() function remains available for backward compatibility, but subprocess.run() is the better choice for new code.

Using subprocess.call() to execute commands

The subprocess.call() function is part of the subprocess module and works similarly to os.system(). It runs a command and returns its exit code. The difference is that it gives you more control over how the command is invoked.

import subprocess

cmd = "python3 --version"

returned_value = subprocess.call(cmd, shell=True)
print("returned value:", returned_value)

Output:

Python 3.14.3
returned value: 0

You can also pass the command as a list of arguments, which avoids using the shell entirely:

import subprocess

returned_value = subprocess.call(["python3", "--version"])
print("returned value:", returned_value)

Output:

Python 3.14.3
returned value: 0

Passing a list is safer because it bypasses shell interpretation. The command and its arguments are sent directly to the operating system without any shell parsing, which eliminates the risk of command injection.

While subprocess.call() improves on os.system(), it still does not let you capture output. For that, you need subprocess.run() or subprocess.check_output().

subprocess.run() was introduced in Python 3.5 and is the recommended way to run external commands. It returns a CompletedProcess object containing the return code, captured standard output, and captured standard error.

Basic usage

import subprocess

result = subprocess.run(["echo", "Hello from subprocess"], capture_output=True, text=True)
print("stdout:", result.stdout.strip())
print("returncode:", result.returncode)

Output:

stdout: Hello from subprocess
returncode: 0

The capture_output=True parameter tells Python to collect both stdout and stderr. Setting text=True returns output as strings instead of raw bytes.

Using check=True for automatic error raising

When you pass check=True, Python raises a subprocess.CalledProcessError if the command exits with a non-zero return code:

import subprocess

try:
    result = subprocess.run(
        ["ls", "/nonexistent_path"],
        capture_output=True,
        text=True,
        check=True
    )
except subprocess.CalledProcessError as e:
    print(f"Command failed with return code: {e.returncode}")
    print(f"stderr: {e.stderr.strip()}")

Output:

Command failed with return code: 1
stderr: ls: /nonexistent_path: No such file or directory

This pattern is much cleaner than manually checking return codes after every command.

Setting a timeout

You can set a maximum execution time for a command using the timeout parameter (in seconds):

import subprocess

try:
    result = subprocess.run(["sleep", "10"], capture_output=True, text=True, timeout=2)
except subprocess.TimeoutExpired as e:
    print(f"Command timed out after {e.timeout} seconds")

Output:

Command timed out after 2 seconds

Timeouts are useful when calling external services or commands that might hang.

Understanding shell=True and its security risks

When you pass shell=True to any subprocess function, the command is executed through the system shell (/bin/sh on Unix, cmd.exe on Windows). This lets you use shell features like pipes, wildcards, and environment variable expansion:

import subprocess

result = subprocess.run("echo $HOME", shell=True, capture_output=True, text=True)
print(result.stdout.strip())

This prints your home directory because the shell interprets $HOME.

The shell=True security problem

Using shell=True with untrusted input creates a command injection vulnerability. Consider this dangerous pattern:

import subprocess

user_input = "hello; rm -rf /tmp/testdir"
subprocess.run(f"echo {user_input}", shell=True)

The semicolon in the user input causes the shell to execute a second, unintended command. This is a well-known attack vector in systems that accept user-supplied data.

shell=True safe alternatives

The safest approach is to pass commands as a list of arguments without shell=True:

import subprocess

user_input = "hello; rm -rf /tmp/testdir"
result = subprocess.run(["echo", user_input], capture_output=True, text=True)
print(result.stdout.strip())

Output:

hello; rm -rf /tmp/testdir

Here, the entire string is treated as a single argument to echo. The shell never interprets the semicolon.

If you need to split a command string into a list of arguments safely, use shlex.split():

import shlex

cmd = 'grep -r "search term" /var/log'
args = shlex.split(cmd)
print(args)

Output:

['grep', '-r', 'search term', '/var/log']

The shlex.split() function handles quoted strings and escape characters correctly, producing a list you can pass directly to subprocess.run().

When is shell=True acceptable? Only use it when you explicitly need shell features (like pipes or globbing) and you fully control the command string with no user-supplied input.

Using subprocess.Popen for advanced process control

subprocess.Popen gives you direct control over a child process. Unlike subprocess.run(), which waits for the command to finish before returning, Popen starts the process and lets you interact with it while it runs.

Basic Popen usage with communicate()

The .communicate() method sends input to the process and waits for it to complete, returning a tuple of (stdout, stderr):

import subprocess

process = subprocess.Popen(
    ["echo", "hello from Popen"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)
stdout, stderr = process.communicate()
print("stdout:", stdout.strip())
print("returncode:", process.returncode)

Output:

stdout: hello from Popen
returncode: 0

Polling a long-running process

Use .poll() to check if a process is still running without blocking:

import subprocess
import time

process = subprocess.Popen(["sleep", "3"])

while process.poll() is None:
    print("Process is still running...")
    time.sleep(1)

print(f"Process finished with return code: {process.returncode}")

Output:

Process is still running...
Process is still running...
Process is still running...
Process finished with return code: 0

Piping between processes

Popen lets you chain commands together, replicating shell pipes:

import subprocess

p1 = subprocess.Popen(["echo", "apple\nbanana\ncherry"], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["grep", "banana"], stdin=p1.stdout, stdout=subprocess.PIPE, text=True)
p1.stdout.close()

output, _ = p2.communicate()
print("Filtered output:", output.strip())

Output:

Filtered output: banana

This approach is safer than using shell=True with a pipe character because each command runs in its own process with no shell interpretation.

When to use Popen vs run()

Use subprocess.run() for most cases where you run a command and wait for the result. Choose Popen when you need to:

  • Stream output from a process in real time
  • Send input to a running process interactively
  • Run multiple processes in parallel
  • Build pipelines between processes

Redirecting stdout, stderr, and stdin

The subprocess module provides fine-grained control over standard streams through the stdout, stderr, and stdin parameters.

Capturing output separately

import subprocess

result = subprocess.run(
    ["ls", "/tmp", "/nonexistent"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)
print("stdout:", result.stdout[:80])
print("stderr:", result.stderr.strip())

This captures standard output and standard error into separate variables, letting you handle success output and error messages independently.

Redirecting output to a file

import subprocess

with open("output.txt", "w") as f:
    subprocess.run(["echo", "writing to file"], stdout=f)

The command output goes directly to output.txt instead of the console.

Combining stdout and stderr

To merge standard error into standard output, use stderr=subprocess.STDOUT:

import subprocess

result = subprocess.run(
    ["ls", "/tmp", "/nonexistent"],
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    text=True
)
print("combined output:", result.stdout)

This is useful when you want a single stream of all output from a command.

Sending input to a command

Use the input parameter to pass data to a command’s standard input:

import subprocess

result = subprocess.run(
    ["grep", "world"],
    input="hello\nworld\nfoo\n",
    capture_output=True,
    text=True
)
print("matched:", result.stdout.strip())

Output:

matched: world

For more complex input handling with the os module, see the Python os module tutorial.

Handling return codes and errors

Every external command returns an exit code. A return code of 0 means success; any non-zero value indicates an error. Python gives you several ways to check and respond to these codes.

Checking the return code manually

import subprocess

result = subprocess.run(["ls", "/nonexistent"], capture_output=True, text=True)

if result.returncode != 0:
    print(f"Command failed (exit code {result.returncode})")
    print(f"Error: {result.stderr.strip()}")
else:
    print("Success:", result.stdout)

Automatic error raising with check=True

As shown earlier, check=True raises subprocess.CalledProcessError for non-zero exit codes. Here is a full example with proper exception handling:

import subprocess

try:
    result = subprocess.run(
        ["python3", "-c", "import sys; sys.exit(42)"],
        capture_output=True,
        text=True,
        check=True
    )
except subprocess.CalledProcessError as e:
    print(f"Return code: {e.returncode}")
    print(f"Command: {e.cmd}")
except FileNotFoundError:
    print("The command was not found on this system")
except subprocess.TimeoutExpired:
    print("The command took too long to complete")

Output:

Return code: 42
Command: ['python3', '-c', 'import sys; sys.exit(42)']

Common exception types and how to fix them

Exception When it occurs How to fix
subprocess.CalledProcessError Command returns a non-zero exit code (with check=True) Investigate the command output via e.stderr/e.output. Double-check command arguments, file paths, and permissions. The fix often involves correcting the command or input data.
FileNotFoundError The executable does not exist on the system Make sure the command or executable is installed and available in the system’s PATH. Use which <command> or install missing binaries as needed. Verify spelling of the program name.
subprocess.TimeoutExpired Command exceeds the specified timeout value Increase the timeout value if your command regularly takes longer to run, or optimize the command to complete more quickly. Optionally, handle cleanup/retry if timeouts are expected.
PermissionError Executable exists but cannot be run due to insufficient permissions Check file permissions with ls -l (on Unix) or via the file’s Properties (on Windows). Use chmod +x <file> to make a file executable, or run the script with elevated privileges (e.g., sudo, or as an Administrator on Windows) if necessary.

Anticipating and handling these exceptions helps make your automation robust, especially when your scripts may encounter different environments or missing dependencies. For more on effective Python error handling, see the how to handle errors in Python tutorial.

Comparison table: os.system vs subprocess methods

Feature os.system() subprocess.call() subprocess.run() subprocess.Popen()
Capture stdout No No Yes (capture_output=True) Yes (stdout=PIPE)
Capture stderr No No Yes (capture_output=True) Yes (stderr=PIPE)
Return code access Yes (return value) Yes (return value) Yes (result.returncode) Yes (process.returncode)
Raise on error No No Yes (check=True) No (manual check)
Timeout support No Yes (timeout param) Yes (timeout param) Manual (.wait(timeout))
Send input (stdin) No No Yes (input param) Yes (stdin=PIPE)
Real-time streaming No No No Yes
Shell required Yes (always) Optional Optional Optional
Recommended for new code No No Yes For advanced cases

For most tasks, subprocess.run() provides the right balance of simplicity and control. Reach for Popen only when you need real-time interaction with a running process.

FAQs

1. What is the difference between os.system() and subprocess.run() in Python?

os.system() passes a command string to the system shell and returns only the exit code. It cannot capture the command’s output or error messages. subprocess.run(), introduced in Python 3.5, returns a CompletedProcess object with the return code, stdout, and stderr. It also supports timeouts, automatic error raising with check=True, and can avoid the shell entirely when you pass a list of arguments. The Python documentation recommends subprocess.run() as the replacement for os.system().

2. Is os.system() deprecated in Python 3?

No, os.system() is not formally deprecated. It still works in Python 3.14 and returns the exit status of the command. However, the official Python documentation notes that the subprocess module provides “more powerful facilities for spawning new processes” and recommends using subprocess.run() instead.

3. When should I use shell=True in subprocess?

Use shell=True only when you need shell-specific features like pipes (|), wildcards (*), or environment variable expansion ($HOME), and you fully control the command string. Never use shell=True when any part of the command comes from user input, because this creates a command injection vulnerability. The safer pattern is to pass a list of arguments (e.g., subprocess.run(["ls", "-la"])) that bypasses the shell entirely.

4. How do I capture the output of a shell command in Python?

Use subprocess.run() with capture_output=True and text=True:

import subprocess

result = subprocess.run(["date"], capture_output=True, text=True)
print(result.stdout.strip())

This stores the command’s standard output in result.stdout as a string. Without text=True, the output is returned as a bytes object. You can also use the older subprocess.check_output() function, which returns the output directly but raises an exception on non-zero exit codes.

5. What does a non-zero return code mean in subprocess?

A non-zero return code means the command did not complete successfully. The specific value depends on the command. For example, grep returns 1 when it finds no matches, and ls returns 2 for serious errors like invalid options. You can check the return code via result.returncode after calling subprocess.run(). If you pass check=True, Python will raise a subprocess.CalledProcessError automatically when the return code is non-zero, which simplifies error handling.

Conclusion

Python’s subprocess module gives you full control over running system commands, from simple one-liners to complex pipelines with real-time output streaming. For new projects, use subprocess.run() as your default. It handles output capture, error checking, and timeouts in a single function call. Reserve subprocess.Popen() for cases that require interacting with a running process, and avoid os.system() in new code since it lacks output capture and proper error handling.

When working with external commands, always prefer passing arguments as a list over using shell=True to protect against command injection. Combine check=True with proper exception handling to write scripts that fail predictably and provide clear error messages.

For working with command-line arguments in your own Python scripts, or for debugging complex programs, the DigitalOcean community has additional tutorials that build on these concepts.

Next steps

If you are building Python automation scripts that run shell commands on remote servers, DigitalOcean Droplets provide a fast way to spin up Linux environments for testing and production. You can deploy a Droplet with Python pre-installed and use the techniques from this tutorial to automate server administration tasks.

Here are some related resources to continue learning:

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.