杰瑞科技汇

Python subprocess如何高效执行外部命令?

Of course! The subprocess module is one of Python's most powerful and essential tools for running external commands from within your script. It replaces older, less secure modules like os.system().

Python subprocess如何高效执行外部命令?-图1
(图片来源网络,侵删)

Let's break it down, from the simple basics to more advanced use cases.

Why Use subprocess?

You use subprocess when you need to:

  • Run a command-line tool (e.g., ls, git, docker, ffmpeg).
  • Interact with another program by sending it input and reading its output.
  • Automate system administration tasks.
  • Chain commands together, just like you would in a terminal.

The Core Idea: Creating a Child Process

When you use subprocess, your Python script becomes the parent process. The command you run becomes a child process. The subprocess module gives you fine-grained control over how this child process is created, how it communicates with you, and how it terminates.


The "Modern" Way: subprocess.run()

For most common use cases, subprocess.run() is the recommended function. It's a versatile and user-friendly wrapper that handles many of the complexities for you.

Python subprocess如何高效执行外部命令?-图2
(图片来源网络,侵删)

Basic Syntax

import subprocess
# The command is a list of strings. The first string is the command, the rest are arguments.
command = ["ls", "-l", "/tmp"]
# Run the command
result = subprocess.run(command)
print(f"Return code: {result.returncode}")

Capturing Output

The child process's output (stdout and stderr) is usually printed to your terminal directly. To capture it in your Python script, you need to use the capture_output=True argument.

import subprocess
command = ["echo", "Hello from subprocess!"]
# capture_output=True captures stdout and stderr
# text=True decodes the output as text (default is bytes)
# check=True will raise an exception if the command returns a non-zero exit code (i.e., fails)
result = subprocess.run(command, capture_output=True, text=True, check=True)
print("--- Capturing Output ---")
print(f"Command: {' '.join(command)}")
print(f"Stdout: {result.stdout}") # The output is in the 'stdout' attribute
print(f"Stderr: {result.stderr}") # Errors are in the 'stderr' attribute
print(f"Return code: {result.returncode}")

Key Arguments for subprocess.run():

  • args: The command to run, as a list of strings.
  • capture_output=True: Captures stdout and stderr.
  • text=True (or universal_newlines=True): Decodes stdout/stderr as text (using the default encoding). If False, they are returned as bytes.
  • check=True: If the command returns a non-zero exit code (indicating an error), it will raise a CalledProcessError exception. This is very useful for error handling.
  • input="some data": Provides data to the command's standard input (stdin).
  • cwd="/path/to/dir": Changes the current working directory for the child process.
  • env={"KEY": "value"}: Specifies custom environment variables for the child process.

The "Old School" Way: subprocess.Popen()

For more complex scenarios where you need to interact with a process in real-time (e.g., a long-running server or an interactive shell), you need subprocess.Popen(). It gives you more control, but it's also more complex.

Popen stands for "process open". It starts the process and immediately returns a Popen object, without waiting for the command to finish.

Python subprocess如何高效执行外部命令?-图3
(图片来源网络,侵删)
import subprocess
# Start the process
# The `shell=True` argument can be useful but is a security risk if you use untrusted input.
# It's generally safer to pass commands as a list.
process = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE, text=True)
# The process is now running in the background.
# We can read its output line by line.
print("--- Reading from Popen object ---")
for line in process.stdout:
    print(line, end='') # `end=''` because `line` already has a newline
# Wait for the process to complete and get its return code
return_code = process.wait()
print(f"\nProcess finished with return code: {return_code}")

Key Arguments for subprocess.Popen():

  • args: The command to run.
  • stdin, stdout, stderr: These can be set to subprocess.PIPE to create a pipe for that stream, allowing you to read from or write to it. They can also be set to subprocess.DEVNULL to discard the stream, or to an existing file object.
  • text=True: Decodes streams as text.

Practical Examples

Example 1: Running a System Command and Checking for Errors

This is a very common pattern. You want to run a command, and if it fails, you want your script to stop and report the error.

import subprocess
print("Attempting to list a non-existent directory...")
try:
    # The 'check=True' argument will raise an error here
    subprocess.run(["ls", "/non_existent_path"], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
    print(f"Command failed with return code {e.returncode}")
    print(f"Error output:\n{e.stderr}")

Example 2: Piping Commands Together

You can chain commands just like in the shell. Here, we'll get a list of Python files in the current directory and count them.

import subprocess
# 1. Get a list of files with 'grep'
grep_process = subprocess.Popen(
    ["ls", "*.py"], 
    stdout=subprocess.PIPE, 
    text=True
)
# 2. Count the lines from the first command's output
wc_process = subprocess.Popen(
    ["wc", "-l"], 
    stdin=grep_process.stdout, # The input for 'wc' is the output of 'ls'
    stdout=subprocess.PIPE, 
    text=True
)
# Close the pipe from the first process to prevent deadlocks
grep_process.stdout.close()
# Get the final output
count_output, _ = wc_process.communicate()
print(f"Number of Python files: {count_output.strip()}")

A simpler way to achieve the same result using shell=True (use with caution!):

# This is simpler but less secure if the command string comes from user input
result = subprocess.run("ls *.py | wc -l", shell=True, capture_output=True, text=True)
print(f"Number of Python files (shell): {result.stdout.strip()}")

Example 3: Sending Input to a Process

Let's run Python's interactive interpreter and give it a command to execute.

import subprocess
# The command is just "python", which will start the REPL
process = subprocess.Popen(
    ["python"], 
    stdin=subprocess.PIPE, 
    stdout=subprocess.PIPE, 
    stderr=subprocess.PIPE, 
    text=True
)
# Send a command to the interpreter's stdin
# The input must be a string
command_to_run = "print('Hello from inside a Python subprocess!')\n"
stdout, stderr = process.communicate(input=command_to_run)
print("--- Output from the child Python process ---")
print(stdout)
if stderr:
    print("--- Errors from the child Python process ---")
    print(stderr)

Security Warning: shell=True

Using shell=True can be convenient for piping () and using shell features (>, &&, ), but it introduces a major security risk called shell injection.

If any part of your command string comes from untrusted input (like a user on a website), a malicious user could inject commands.

Vulnerable Code:

import subprocess
user_input = "malicious_file.txt; rm -rf /" # A malicious user could provide this
# DANGEROUS! This will run "ls -l malicious_file.txt" AND "rm -rf /"
subprocess.run(f"ls -l {user_input}", shell=True)

Safe Code:

Always prefer passing arguments as a list. The subprocess module will handle the quoting and escaping for you.

import subprocess
user_input = "malicious_file.txt; rm -rf /" # The same malicious input
# SAFE! The shell will not interpret the semicolons or special characters.
# It will literally try to find a file named "malicious_file.txt; rm -rf /"
subprocess.run(["ls", "-l", user_input])

Summary: Which Function to Use?

Function When to Use It Key Feature
subprocess.run() Your default choice. For running commands, capturing output, and checking for errors. Simple, high-level, secure.
subprocess.Popen() For advanced use cases: long-running processes, real-time interaction, or complex piping. Gives you a process object to control.
subprocess.check_output() A shortcut for subprocess.run(..., check=True, stdout=PIPE). When you only care about stdout and want an error on failure. Simpler than run() for this specific case.
subprocess.call() A shortcut for subprocess.run(..., check=False). When you only care about the return code and don't need output. Simpler than run() for this specific case.
分享:
扫描分享到社交APP
上一篇
下一篇