How can I call a shell script from Python code?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The standard way to call a shell script from Python is the subprocess module. It gives you control over arguments, exit codes, output capture, environment variables, and timeouts, which makes it safer and more maintainable than older helpers such as os.system.
Running a script directly with subprocess.run
If your script is executable and has a correct shebang, call it as a normal program:
This is the preferred form because Python passes the command and arguments directly without invoking a shell. That means fewer quoting bugs and much lower risk of shell injection.
For this to work, the script should be executable:
And it should declare an interpreter, for example:
Running the script through a shell
Sometimes you really do need shell features such as pipes, wildcards, or variable expansion. In that case, call the shell explicitly:
This is still safer than shell=True because you are invoking bash directly with a fixed argument list.
Use shell=True only when you need shell syntax in a single command string:
That form is flexible, but it requires careful input handling.
Passing environment variables and working directories
Real scripts often depend on environment variables or a specific current directory. subprocess.run supports both cleanly:
This is better than changing global process state with os.chdir or mutating os.environ in a way that leaks into the rest of your program.
Handling failures properly
When check=True is set, Python raises subprocess.CalledProcessError if the script exits with a non-zero status. That is usually what you want:
You can also add a timeout:
That prevents a hung shell script from blocking the Python process forever.
If you need to stream output while the script runs, use subprocess.Popen instead of run. That gives you access to the process object and lets you read stdout incrementally, which is useful for long-running deployment or build scripts.
Common Pitfalls
The biggest mistake is building a shell command by concatenating untrusted input into a string. If user-controlled data ends up inside a shell=True command, you have created a command injection risk.
Another common issue is forgetting execute permissions or the shebang line. In that case, the script exists, but the operating system does not know how to run it.
Relative paths also cause trouble. A script that works from your terminal may fail from Python because the current working directory is different. Use cwd= or absolute paths when the script depends on local files.
Finally, do not use os.system for new code. It gives you almost no control over stdout, stderr, structured arguments, or error handling compared with subprocess.
One more subtle issue is platform assumptions. A script that depends on Bash-specific syntax may fail on systems where /bin/sh points to a different shell, so be explicit about the interpreter when the script requires it.
Summary
- Use
subprocess.runfor calling shell scripts from Python. - Prefer passing an argument list such as
["./script.sh", "arg"]overshell=True. - Call
bashexplicitly when you need a shell but want predictable argument handling. - Use
cwd,env,capture_output,check, andtimeoutto make script execution reliable. - Avoid
os.systemand avoid building shell command strings from untrusted input.

