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
Validate the Path Before Running a Command
Input ValidationCheck 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 Pathfrom 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/dfStep 2: Call the Program Without a Shell
Pass Arguments as a List and Set a Timeout
Process ExecutionPass 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
Report Failures Without Hiding Standard Error
Error HandlingA 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
Log the Action and Keep Arguments Visible
OperationsLog 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/dfSee Python’s subprocess documentation for timeout, output, exit-status, and security details.