The subprocess
module in Python is a powerful utility for spawning new processes, connecting to their input/output/error pipes, and obtaining their return codes. It is a flexible and essential tool for interacting with the operating system, running shell commands, and managing subprocesses. This article will provide an in-depth guide to using the subprocess
module, covering basic usage, advanced features, and best practices.
Overview of the subprocess
Module
The subprocess
module supersedes older modules and functions such as os.system
, os.spawn*
, and the commands
module. It provides a unified interface for creating and working with additional processes. Key components and functions in the subprocess
module include:
subprocess.run()
subprocess.Popen()
subprocess.PIPE
subprocess.CompletedProcess
subprocess.CalledProcessError
Basic Usage
Running a Simple Command
The subprocess.run()
function is the recommended way to run a command in Python. It was introduced in Python 3.5 and provides a simple interface for common use cases.
x = 10import subprocess
result = subprocess.run(['ls', '-l'], capture_output=True, text=True)
print(result.stdout)
del x
In this example:
subprocess.run()
runs thels -l
command.capture_output=True
captures the command’s standard output and standard error.text=True
returns the output as a string rather than bytes.result.stdout
contains the command’s standard output.
Checking for Errors
By default, subprocess.run()
does not raise an exception if the command returns a non-zero exit code. You can use the check=True
argument to raise an exception in case of an error.
import subprocess
try:
result = subprocess.run(['ls', '-l', 'nonexistentfile'], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
print(f"Command failed with exit code {e.returncode}")
print(e.stderr)
In this example:
check=True
causessubprocess.run()
to raise asubprocess.CalledProcessError
if the command fails.- The exception object
e
contains the return code and the standard error output.
Advanced Usage
Interacting with the Process
If you need more control over the process, you can use the subprocess.Popen
class. This allows you to interact with the process’s input/output/error pipes directly.
import subprocess
process = subprocess.Popen(['grep', 'foo'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
stdout, stderr = process.communicate(input='foo\nbar\nbaz\nfoo\n')
print(stdout)
In this example:
subprocess.Popen
starts thegrep foo
command.stdin=subprocess.PIPE
andstdout=subprocess.PIPE
allow you to send input to and read output from the process.process.communicate()
sends input to the process and reads its output.
Piping Between Commands
You can chain multiple commands together using pipes.
import subprocess
p1 = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE)
p2 = subprocess.Popen(['grep', '.py'], stdin=p1.stdout, stdout=subprocess.PIPE, text=True)
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
output, _ = p2.communicate()
print(output)
In this example:
- The output of
ls -l
is piped into thegrep .py
command. p1.stdout.close()
ensures thatp1
can terminate ifp2
exits early.
Using Shell Commands
You can run commands through the shell by setting shell=True
. This is useful for running shell-specific syntax like wildcard expansion or command chaining.
import subprocess
result = subprocess.run('echo $HOME && ls -l', shell=True, capture_output=True, text=True)
print(result.stdout)
In this example:
shell=True
runs the command through the shell, allowing the use of shell features.- Use caution with
shell=True
to avoid security risks, especially with user-provided input.
Handling Timeouts
You can specify a timeout for the command using the timeout
parameter.
import subprocess
try:
result = subprocess.run(['sleep', '10'], timeout=5)
except subprocess.TimeoutExpired:
print("Command timed out")
In this example:
- The
sleep 10
command is expected to run for 10 seconds, but a timeout of 5 seconds is specified. - A
subprocess.TimeoutExpired
exception is raised if the command exceeds the timeout.
Working with Input and Output
Capturing Output
To capture the output of a command, you can use the stdout
and stderr
parameters.
import subprocess
result = subprocess.run(['ls', '-l'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print(result.stdout)
print(result.stderr)
Redirecting Output
You can redirect output to a file.
import subprocess
with open('output.txt', 'w') as f:
subprocess.run(['ls', '-l'], stdout=f)
In this example:
- The output of the
ls -l
command is written tooutput.txt
.
Providing Input
You can provide input to a command using the input
parameter of subprocess.run()
.
import subprocess
result = subprocess.run(['grep', 'foo'], input='foo\nbar\nbaz\nfoo\n', text=True, capture_output=True)
print(result.stdout)
Error Handling
Checking Exit Codes
You can check the exit code of a command using the returncode
attribute.
import subprocess
result = subprocess.run(['ls', '-l'])
if result.returncode != 0:
print(f"Command failed with exit code {result.returncode}")
Handling Exceptions
The subprocess
module raises different exceptions for various errors:
subprocess.CalledProcessError
: Raised when a process returns a non-zero exit code ifcheck=True
is specified.subprocess.TimeoutExpired
: Raised when a timeout expires.subprocess.SubprocessError
: The base class for all exceptions in thesubprocess
module.
import subprocess
try:
result = subprocess.run(['false'], check=True)
except subprocess.CalledProcessError as e:
print(f"Command failed with exit code {e.returncode}")
except subprocess.TimeoutExpired:
print("Command timed out")
except subprocess.SubprocessError as e:
print(f"Subprocess error: {e}")
Best Practices
Avoid Using shell=True
When Possible
Using shell=True
can expose your code to shell injection vulnerabilities, especially if you pass user-provided input to the shell. Use a list of arguments instead.
# Avoid this
subprocess.run('echo $HOME', shell=True)
# Use this
subprocess.run(['echo', '$HOME'])
Handle Long-Running Processes
For long-running processes, consider handling timeouts and managing resources properly.
import subprocess
try:
result = subprocess.run(['some_long_running_command'], timeout=300) # 5 minutes
except subprocess.TimeoutExpired:
print("Command timed out")
Clean Up Resources
Ensure that file descriptors and other resources are properly cleaned up after the subprocess completes.
import subprocess
with subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE) as proc:
output = proc.stdout.read()
print(output.decode())
Examples
Example 1: Running a Command and Capturing Output
import subprocess
result = subprocess.run(['ls', '-l'], capture_output=True, text=True)
print(result.stdout)
Example 2: Running a Command with Input
import subprocess
result = subprocess.run(['grep', 'foo'], input='foo\nbar\nbaz\nfoo\n', text=True, capture_output=True)
print(result.stdout)
Example 3: Chaining Commands with Pipes
import subprocess
p1 = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE)
p2 = subprocess.Popen(['grep', '.py'], stdin=p1.stdout, stdout=subprocess.PIPE, text=True)
p1.stdout.close()
output, _ = p2.communicate()
print(output)
Example 4: Handling Timeouts
import subprocess
try:
result = subprocess.run(['sleep', '10'], timeout=5)
except subprocess.TimeoutExpired:
print("Command timed out")
Example 5: Redirecting Output to a File
import subprocess
with open('output.txt', 'w') as f:
subprocess.run(['ls', '-l'], stdout=f)
Example 6: Checking Exit Codes
import subprocess
result = subprocess.run(['ls', '-l'])
if result.returncode != 0:
print(f"Command failed with exit code {result.returncode}")
Example 7: Handling Subprocess Exceptions
import subprocess
try:
result = subprocess.run(['false'], check=True)
except subprocess.CalledProcessError as e:
print(f"Command failed with exit code {e.returncode}")
except subprocess.TimeoutExpired:
print("Command timed out")
except subprocess.SubprocessError as e:
print(f"Subprocess error: {e}")
The subprocess
module in Python is a versatile and powerful tool for managing subprocesses and interacting with the operating system. It provides a range of functionalities from simple command execution to complex process management, input/output handling, and error handling.