Rewrite a fragile shell script in Python
Task
Take a shell script that parses structured data and calls external commands, and rewrite it in Python at the point where shell stops being the right tool -- around JSON, error handling and anything past a hundred lines. Do it safely, with a venv, argparse and subprocess used correctly.
Steps
- Start from a shell script that queries something returning JSON -- e.g.
podman inspector a REST endpoint -- and parses it with grep and cut. Note where it is fragile: a field containing a space, a missing key, a value with a slash. - Rewrite it in Python: parse the JSON with the
jsonmodule rather than text tools, and handle the missing-key case with.get()and a default. - Call external commands with
subprocess.runpassing a LIST, not a string, and nevershell=True. Demonstrate why: show that a value containing; rm -rf xis harmless in the list form and dangerous withshell=True(test the dangerous form only with a harmless payload like; echo pwned). - Add argument parsing with
argparseso the script gets--help, type checking and required arguments for free. - Check exit codes: inspect
subprocess.run(...).returncodeand fail the script clearly when a called command fails. - Add logging with the
loggingmodule rather than print, so output can go to stderr with timestamps. - Record dependencies with
pip freezeand confirm the script runs from a freshly rebuilt venv.
Verify
source ~/.venvs/rewrite/bin/activate
python tool.py --help | grep -qi usage && echo "argparse wired"
python tool.py --target demo | head
# injection safety: list form must NOT execute the payload
python - <<'PY'
import subprocess
r = subprocess.run(["echo", "; echo pwned"], capture_output=True, text=True)
print("safe" if "pwned" not in r.stdout.split(";",1)[0] else "check")
PY
The injection test is the point: passing a list hands the arguments straight to exec with no shell to interpret the semicolon, so a hostile value is inert. shell=True would have run it. This is the quoting lesson from bash, with sharper teeth.
Notes
The honest trigger for the rewrite is a length: past roughly a hundred lines, or the moment a shell script starts parsing JSON, Python earns its place. Below that, shell is fine and the rewrite is overhead.