Command-line scripts are easier to reuse when named options replace a fragile positional-only interface. The Zsh getopts builtin reads short options one at a time while leaving the remaining arguments available to the rest of the script.

Each successful call to getopts writes an option letter to the loop variable, stores its value in OPTARG, and advances OPTIND to the next unparsed argument. A colon after an option letter means that option requires a value, so r: accepts both -r eu-west-1 and -reu-west-1.

Silent mode keeps option errors under the script's control instead of mixing builtin diagnostics with its interface. The finished parser accepts -n for dry-run mode, -r for a region override, and an environment name after the options; a leading colon in :nr: routes missing values and unknown options to explicit error branches.

Steps to use getopts in Zsh:

  1. Create deploy-options.zsh with its interpreter, local emulation, and default option values.
    deploy-options.zsh
    #!/usr/bin/env zsh
    emulate -L zsh
    setopt err_exit no_unset
     
    region="us-east-1"
    dry_run=0

    emulate -L zsh gives the script local Zsh option settings, while no_unset stops accidental reads of undefined parameters.

  2. Append the recognized-option loop after the default values.
    while getopts ":nr:" opt; do
        case $opt in
            n)
                dry_run=1
                ;;
            r)
                region=$OPTARG
                ;;
        esac
    done

    Each successful call to getopts supplies one option letter. The -n branch changes a flag, and the -r branch reads its required value from OPTARG.

  3. Insert the custom error branches before the esac line.
            :)
                print -u2 -- "missing value for -$OPTARG"
                exit 2
                ;;
            \?)
                print -u2 -- "unknown option: -$OPTARG"
                exit 2
                ;;

    The leading colon in :nr: sets the loop variable to : for a missing value and ? for an unknown option. In both cases, OPTARG contains the affected option letter.

  4. Append the remaining-argument shift and usage guard after the option loop.
    shift $(( OPTIND - 1 ))
    environment=${1-}
     
    if [[ -z $environment ]]; then
        print -u2 -- 'usage: deploy-options.zsh [-n] [-r region] environment'
        exit 2
    fi

    OPTIND points to the first argument that getopts did not consume. After the shift, $1 is the environment name rather than an option or its value.

  5. Append the parsed values after the usage guard.
    print -r -- "environment=$environment"
    print -r -- "region=$region"
    print -r -- "dry_run=$dry_run"
  6. Compare deploy-options.zsh with the completed parser.
    deploy-options.zsh
    #!/usr/bin/env zsh
    emulate -L zsh
    setopt err_exit no_unset
     
    region="us-east-1"
    dry_run=0
     
    while getopts ":nr:" opt; do
        case $opt in
            n)
                dry_run=1
                ;;
            r)
                region=$OPTARG
                ;;
            :)
                print -u2 -- "missing value for -$OPTARG"
                exit 2
                ;;
            \?)
                print -u2 -- "unknown option: -$OPTARG"
                exit 2
                ;;
        esac
    done
     
    shift $(( OPTIND - 1 ))
    environment=${1-}
     
    if [[ -z $environment ]]; then
        print -u2 -- 'usage: deploy-options.zsh [-n] [-r region] environment'
        exit 2
    fi
     
    print -r -- "environment=$environment"
    print -r -- "region=$region"
    print -r -- "dry_run=$dry_run"
  7. Check deploy-options.zsh for Zsh syntax errors.
    $ zsh -n deploy-options.zsh

    No output means Zsh parsed the file without finding a syntax error.

  8. Run the parser with both options before the remaining environment argument.
    $ zsh deploy-options.zsh -n -r eu-west-1 staging
    environment=staging
    region=eu-west-1
    dry_run=1
  9. Trigger the missing-value branch for the region option.
    $ zsh deploy-options.zsh -r
    missing value for -r
  10. Trigger the unknown-option branch with an unsupported letter.
    $ zsh deploy-options.zsh -x staging
    unknown option: -x
  11. Rerun the parser with both valid options and the remaining environment argument.
    $ zsh deploy-options.zsh -n -r eu-west-1 staging
    environment=staging
    region=eu-west-1
    dry_run=1