Write a robust argument-parsing script

applied · 40 min · Objective 4.2

Task

Write a script that parses options and arguments properly, validates its input, handles the environment cron will give it, and fails safely rather than destructively. This assembles the operators, variables, return codes and set -euo pipefail into the shape real scripts take.

Steps

  1. Start the script with #!/usr/bin/env bash and set -euo pipefail, and explain what each of the three does.
  2. Parse options with a while/case/shift loop: a -v flag, a -f FILE option taking an argument, and a -- terminator. Reject unknown options with a usage message on stderr and a non-zero exit.
  3. Validate required arguments with ${1:?usage...} so the script refuses to run with a missing path rather than acting on an empty one.
  4. Demonstrate the safety of that: show that with the target unset, the script exits with the usage message instead of doing anything.
  5. Use a function with local variables, and prove the point by showing a version without local clobbering a caller's loop variable.
  6. Capture a command's exit status correctly -- into a variable on the next line, or by testing the command directly with if ! -- and show the one-line-too-late $? bug.
  7. Make it cron-safe: set PATH at the top and use absolute paths, then run it with a deliberately minimal PATH to prove it still works.

Verify

cd ~/lab-args
./tool.sh 2>&1 | grep -qi usage && echo "refuses with no args"
./tool.sh -f data.txt -v && echo "parses options"
./tool.sh --unknown 2>&1 | grep -qi 'unknown' && echo "rejects unknown option"
env -i PATH=/usr/bin:/bin ./tool.sh -f data.txt && echo "cron-safe PATH"

All four must hold. The last is the one that catches people: a script that works interactively and fails from cron almost always relies on a PATH or a variable that the interactive shell provided and cron does not.

Notes

${1:?message} is the compact form of "refuse rather than guess". A script that acts on an empty variable is one rm -rf "$dir"/ away from deleting the wrong thing; a script that refuses to start with an empty variable cannot be.