Skip to content
shellmap

getoptsParse short option flags inside a shell script across all 5 shells

Equivalents in every shell

Bashunix
while getopts "ab:c" opt; do ...; done

POSIX builtin. Single-character short flags only (no `--long`). A trailing `:` on a letter means that flag takes an argument: `b:` consumes the next arg into `$OPTARG`. `$OPTIND` advances; reset to `1` before re-parsing.

Zshunix
while getopts "ab:c" opt; do ...; done

Same POSIX behaviour as bash. Zsh additionally ships `zparseopts`, a richer parser that supports long flags (`--verbose`), repeated options, and value-or-arg patterns. Prefer `zparseopts` in zsh-only scripts.

Fishunix
argparse 'h/help' 'f/file=' -- $argv

Fish has NO `getopts`. The native primitive is `argparse`, which supports BOTH short and long forms: `argparse h/help v/verbose 'f/file=' -- $argv`. Each flag becomes a `$_flag_NAME` variable after parsing.

PowerShellwindows
param([switch]$a, [string]$b, [switch]$c)

PowerShell scripts use `param()` + parameter attributes (`[Parameter()]`, `[ValidateSet()]`). The runtime binds positional and named args automatically — no manual loop required, and long flags work natively.

cmd.exewindows
:parse

Cmd has no flag parser. Manual `if "%~1"=="-a"` blocks plus `shift` is the only approach. Use PowerShell for any script with more than one or two flags — cmd flag-parsing is famously brittle and stacked short flags (`-abc`) are impossible to handle cleanly.

Worked examples

Parse a `-v` flag and a `-f FILE` option

Bash
while getopts "vf:" opt; do case $opt in v) VERBOSE=1;; f) FILE=$OPTARG;; esac; done
Zsh
while getopts "vf:" opt; do case $opt in v) VERBOSE=1;; f) FILE=$OPTARG;; esac; done
Fish
argparse 'v/verbose' 'f/file=' -- $argv; set -q _flag_verbose; and echo on
PowerShell
param([switch]$Verbose, [string]$File)

Reset OPTIND to call getopts twice in the same script

Bash
OPTIND=1; while getopts "ab:" opt; do ...; done
Zsh
OPTIND=1; while getopts "ab:" opt; do ...; done

Accept long flags as well as short

Bash
while getopts "vf:-:" opt; do case "$opt$OPTARG" in -verbose) VERBOSE=1;; esac; done
Fish
argparse v/verbose f/file= -- $argv
PowerShell
param([Alias('v')][switch]$Verbose)

Gotchas

  • POSIX `getopts` accepts only SHORT flags (`-x`). For long flags (`--verbose`) it requires a hacky `-:` placeholder trick or moving to the EXTERNAL GNU `getopt` (different program, different behaviour, not portable). `argparse` (fish) and `param()` (PowerShell) handle long flags natively.
  • `OPTIND` is a GLOBAL shell variable persisted across `getopts` calls. Forgetting to `OPTIND=1` before re-parsing in the same script causes the second pass to silently skip arguments that the first pass already consumed.
  • Leading `:` in the option string (`getopts ":ab:" opt`) enables SILENT error reporting — getopts no longer writes its own "illegal option" message to stderr. Scripts that want fully custom error UX should use this form, not the default.
  • PowerShell `param()` MUST be the FIRST executable statement in a script (after `using` and `#requires`). Defining it later silently fails — the parameters never bind and the script falls back to reading `$args` as positional.
  • Cmd `%~1` strips quotes from arg 1 but does NOT parse flags. Stacked short flags (`-abc` for three flags in one arg) are impossible to handle in cmd without a full hand-rolled tokenizer; PowerShell or a real scripting language is the practical answer.

WSL & PowerShell Core notes

pwshPowerShell parameter binding works identically on Windows, Linux, and macOS pwsh. Scripts using `param([Parameter(Mandatory)])` are fully portable. Bash `getopts` scripts ported to pwsh almost always become shorter and clearer using `param()` + attributes instead of a hand-rolled loop.
WSLInside WSL bash, `getopts` works exactly as on native Linux. Calling a bash script from PowerShell via `wsl ./script.sh -v -f path` correctly passes flags through — WSL translates `\` and forward slashes in path arguments but leaves dash-prefixed flags alone.

Related commands