Skip to content

Run System Commands Safely from Python

Python can coordinate existing operating-system tools, but launching commands carelessly can create hangs or shell-injection risks. Use subprocess.run with an argument list, a timeout, and explicit output handling. Avoid building a command string from user-supplied input.

The example runs df to report filesystem usage on Linux. Use a tested, platform-specific command for other operating systems; the safe argument-list and error-handling pattern still applies.


Step 1: Confirm the Input and Executable

01

Validate the Path Before Running a Command

Input Validation

Check that the input path exists before starting the child process. Use shutil.which to check that the executable is available in the current environment. Resolving the path also makes the report easier to understand.

from pathlib import Path
from shutil import which
target = Path("/var/log").resolve()
df = which("df")
if not target.exists():
raise FileNotFoundError(target)
if df is None:
raise RuntimeError("df was not found in PATH")
print(f"Inspecting {target} with {df}")
❯ View Expected Console Output
Inspecting /var/log with /usr/bin/df

Step 2: Call the Program Without a Shell

02

Pass Arguments as a List and Set a Timeout

Process Execution

Pass each argument as a separate list item. With shell=False (the default), Python does not ask a shell to interpret metacharacters in the path. A timeout bounds the wait; catch TimeoutExpired so the operator receives a concise diagnostic.

import subprocess
command = [df, "-hP", "--", str(target)]
try:
result = subprocess.run(
command,
capture_output=True,
text=True,
timeout=15,
check=False,
)
except subprocess.TimeoutExpired as error:
raise TimeoutError(f"df exceeded the 15-second limit for {target}") from error
❯ View Expected Console Output
The process output is available as result.stdout and result.stderr.

Step 3: Handle Exit Codes and Diagnostics

03

Report Failures Without Hiding Standard Error

Error Handling

A nonzero return code means the tool reported failure. Include standard error in the diagnostic and stop the automation rather than treating partial output as a valid report. If you set check=True, Python instead raises CalledProcessError for a nonzero exit.

if result.returncode != 0:
raise RuntimeError(
f"df failed ({result.returncode}): {result.stderr.strip()}"
)
print(result.stdout, end="")
❯ View Expected Console Output
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 120G 44G 70G 39% /

Step 4: Add Logging and Keep the Command Auditable

04

Log the Action and Keep Arguments Visible

Operations

Log which trusted executable and arguments are being used, but do not log secrets. Keep the program name and arguments as distinct values until the call is made. If the script accepts paths from users, validate the allowed path scope before passing them to a command.

import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logging.info("Running %s", command[0])
logging.info("Command completed with exit code %s", result.returncode)
❯ View Expected Console Output
2026-10-04 10:30:00 INFO Running /usr/bin/df

See Python’s subprocess documentation for timeout, output, exit-status, and security details.

Comments