Python subprocess Without a Shell
Automation spends a lot of its time running other programs. subprocess.run with a list of arguments does that with no shell involved, which means no quoting, no glob expansion and no injection. shell=True hands your string to /bin/sh instead - and this guide shows exactly what that costs, with a captured command injection that removes a file it was never asked to touch.
OS and Process Automation Guide 17 of 39 Intermediate
- Control nodeUbuntu 26.04 LTS
- Python3.14.4
- requests2.34.2
- paramiko5.0.0
- TimeAbout 18 min
subprocess.run has been the recommended interface since Python 3.5, and capture_output=True since 3.7. os.system and the old commands module are gone or should be treated as such.
- pytest9.1.1
- PyYAML6.0.3
- boto3 / botocore1.43.78
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| RUNNER01 | 192.168.0.27 | Ubuntu 26.04 LTS | Control node - every script in this path runs here | 2 Core | 4 GB | 50 GB |
This guide includes
Use this because automation spends much of its time running other programs. This matters because shell=True turns a hostname read from a config file into a security decision - and passing a list of arguments avoids it entirely.
- learning the one
subprocess.runcall worth remembering - passing a list rather than a string, and seeing what a shell would have done to it
- understanding why
shell=Trueis a security decision - meeting the two completely different failures for a command that does not exist
- building a pipeline without a shell, and using
shlexwhen a command really does arrive as a string
Before you start
-
The one call worth remembering
-
A list, not a string
-
What a shell would have done to that
-
And why shell=True is a security decision
-
The command that does not exist
-
A pipeline, without a shell
-
shlex, when a command really does arrive as a string