Write a robust argument-parsing script
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
- Start the script with
#!/usr/bin/env bashandset -euo pipefail, and explain what each of the three does. - Parse options with a
while/case/shiftloop: a-vflag, a-f FILEoption taking an argument, and a--terminator. Reject unknown options with a usage message on stderr and a non-zero exit. - Validate required arguments with
${1:?usage...}so the script refuses to run with a missing path rather than acting on an empty one. - Demonstrate the safety of that: show that with the target unset, the script exits with the usage message instead of doing anything.
- Use a function with
localvariables, and prove the point by showing a version withoutlocalclobbering a caller's loop variable. - 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. - Make it cron-safe: set
PATHat the top and use absolute paths, then run it with a deliberately minimalPATHto 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.