Skip to content
AgileLogics
6 min read

Parsing Options with getopts

Prerequisites: Script arguments, The case statement

Real command-line tools take options — -v for verbose, -o file for output. getopts parses them reliably.

Example

cat > resize.sh <<'EOF'
#!/usr/bin/env bash
usage() { echo "Usage: $0 [-v] [-w width] file..." >&2; exit 2; }

verbose=false
width=800

while getopts ":vw:h" opt; do
  case "$opt" in
    v) verbose=true ;;
    w) width=$OPTARG ;;
    h) usage ;;
    :) echo "Option -$OPTARG needs a value" >&2; usage ;;
    \?) echo "Unknown option -$OPTARG" >&2; usage ;;
  esac
done
shift $(( OPTIND - 1 ))

(( $# > 0 )) || usage

for file in "$@"; do
  $verbose && echo "Resizing $file to ${width}px"
done
echo "Done: $# file(s)"
EOF

bash resize.sh -v -w 1200 a.jpg "b c.jpg"
bash resize.sh a.jpg
bash resize.sh -x a.jpg
echo "exit code: $?"
Resizing a.jpg to 1200px
Resizing b c.jpg to 1200px
Done: 2 file(s)
Done: 1 file(s)
Unknown option -x
Usage: resize.sh [-v] [-w width] file...
exit code: 2

How it works:

  • ":vw:h" lists the options. A colon after a letter (w:) means it takes a value, available in $OPTARG. The leading colon enables custom error handling (the : and ? cases).
  • The loop runs once per option; case handles each one.
  • shift $(( OPTIND - 1 )) removes the parsed options, leaving just the remaining arguments (the files) in "$@".
  • Short options can be combined: -vw 1200 works too.

Long options

getopts only supports short options. For long options like --width, loop over the arguments yourself:

parse() {
  local width=800 dry_run=false
  while (( $# > 0 )); do
    case "$1" in
      --width) width=$2; shift 2 ;;
      --width=*) width=${1#*=}; shift ;;
      --dry-run) dry_run=true; shift ;;
      --) shift; break ;;
      -*) echo "Unknown option: $1" >&2; return 2 ;;
      *) break ;;
    esac
  done
  echo "width=$width dry_run=$dry_run files=$*"
}

parse --width 640 --dry-run photo.jpg
parse --width=1024 a.jpg b.jpg
width=640 dry_run=true files=photo.jpg
width=1024 dry_run=false files=a.jpg b.jpg

-- conventionally marks the end of options, so a file named -report isn't mistaken for an option.

Always provide --help

A usage message listing every option, with an example, turns a personal script into a tool others can use.