I usually go with something like this:
set -eu
function eusage() {
echo "Usage: $0 <input-file> <output-file>" >&2
echo "Error: $@" >&2
exit 1
}
infile=${1:-}; shift || eusage "Missing input filename"
outfile=${1:-}; shift || eusage "Missing output filename"
The `${1:-}` in this case evaluates to an empty string if `$1` is not set, but `shift` fails when there is no argument to remove.