Rewrite a fragile shell script in Python

applied · 45 min · Objective 4.3

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

  1. Start from a shell script that queries something returning JSON -- e.g. podman inspect or 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.
  2. Rewrite it in Python: parse the JSON with the json module rather than text tools, and handle the missing-key case with .get() and a default.
  3. Call external commands with subprocess.run passing a LIST, not a string, and never shell=True. Demonstrate why: show that a value containing ; rm -rf x is harmless in the list form and dangerous with shell=True (test the dangerous form only with a harmless payload like ; echo pwned).
  4. Add argument parsing with argparse so the script gets --help, type checking and required arguments for free.
  5. Check exit codes: inspect subprocess.run(...).returncode and fail the script clearly when a called command fails.
  6. Add logging with the logging module rather than print, so output can go to stderr with timestamps.
  7. Record dependencies with pip freeze and 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.