How do I parse command line arguments in Bash? How do I parse command line arguments in Bash? bash bash

How do I parse command line arguments in Bash?


Bash Space-Separated (e.g., --option argument)

cat >/tmp/demo-space-separated.sh <<'EOF'#!/bin/bashPOSITIONAL=()while [[ $# -gt 0 ]]; do  key="$1"  case $key in    -e|--extension)      EXTENSION="$2"      shift # past argument      shift # past value      ;;    -s|--searchpath)      SEARCHPATH="$2"      shift # past argument      shift # past value      ;;    -l|--lib)      LIBPATH="$2"      shift # past argument      shift # past value      ;;    --default)      DEFAULT=YES      shift # past argument      ;;    *)    # unknown option      POSITIONAL+=("$1") # save it in an array for later      shift # past argument      ;;  esacdoneset -- "${POSITIONAL[@]}" # restore positional parametersecho "FILE EXTENSION  = ${EXTENSION}"echo "SEARCH PATH     = ${SEARCHPATH}"echo "LIBRARY PATH    = ${LIBPATH}"echo "DEFAULT         = ${DEFAULT}"echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)if [[ -n $1 ]]; then    echo "Last line of file specified as non-opt/last argument:"    tail -1 "$1"fiEOFchmod +x /tmp/demo-space-separated.sh/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
Output from copy-pasting the block above
FILE EXTENSION  = confSEARCH PATH     = /etcLIBRARY PATH    = /usr/libDEFAULT         =Number files in SEARCH PATH with EXTENSION: 14Last line of file specified as non-opt/last argument:#93.184.216.34    example.com
Usage
demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

Bash Equals-Separated (e.g., --option=argument)

cat >/tmp/demo-equals-separated.sh <<'EOF'#!/bin/bashfor i in "$@"; do  case $i in    -e=*|--extension=*)      EXTENSION="${i#*=}"      shift # past argument=value      ;;    -s=*|--searchpath=*)      SEARCHPATH="${i#*=}"      shift # past argument=value      ;;    -l=*|--lib=*)      LIBPATH="${i#*=}"      shift # past argument=value      ;;    --default)      DEFAULT=YES      shift # past argument with no value      ;;    *)      # unknown option      ;;  esacdoneecho "FILE EXTENSION  = ${EXTENSION}"echo "SEARCH PATH     = ${SEARCHPATH}"echo "LIBRARY PATH    = ${LIBPATH}"echo "DEFAULT         = ${DEFAULT}"echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)if [[ -n $1 ]]; then    echo "Last line of file specified as non-opt/last argument:"    tail -1 $1fiEOFchmod +x /tmp/demo-equals-separated.sh/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
Output from copy-pasting the block above
FILE EXTENSION  = confSEARCH PATH     = /etcLIBRARY PATH    = /usr/libDEFAULT         =Number files in SEARCH PATH with EXTENSION: 14Last line of file specified as non-opt/last argument:#93.184.216.34    example.com
Usage
demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

To better understand ${i#*=} search for "Substring Removal" in this guide. It is functionally equivalent to `sed 's/[^=]*=//' <<< "$i"` which calls a needless subprocess or `echo "$i" | sed 's/[^=]*=//'` which calls two needless subprocesses.


Using bash with getopt[s]

getopt(1) limitations (older, relatively-recent getopt versions):

  • can't handle arguments that are empty strings
  • can't handle arguments with embedded whitespace

More recent getopt versions don't have these limitations. For more information, see these docs.


POSIX getopts

Additionally, the POSIX shell and others offer getopts which doen't have these limitations. I've included a simplistic getopts example.

cat >/tmp/demo-getopts.sh <<'EOF'#!/bin/sh# A POSIX variableOPTIND=1         # Reset in case getopts has been used previously in the shell.# Initialize our own variables:output_file=""verbose=0while getopts "h?vf:" opt; do  case "$opt" in    h|\?)      show_help      exit 0      ;;    v)  verbose=1      ;;    f)  output_file=$OPTARG      ;;  esacdoneshift $((OPTIND-1))[ "${1:-}" = "--" ] && shiftecho "verbose=$verbose, output_file='$output_file', Leftovers: $@"EOFchmod +x /tmp/demo-getopts.sh/tmp/demo-getopts.sh -vf /etc/hosts foo bar
Output from copy-pasting the block above
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
Usage
demo-getopts.sh -vf /etc/hosts foo bar

The advantages of getopts are:

  1. It's more portable, and will work in other shells like dash.
  2. It can handle multiple single options like -vf filename in the typical Unix way, automatically.

The disadvantage of getopts is that it can only handle short options (-h, not --help) without additional code.

There is a getopts tutorial which explains what all of the syntax and variables mean. In bash, there is also help getopts, which might be informative.


No answer showcases enhanced getopt. And the top-voted answer is misleading: It either ignores -⁠vfd style short options (requested by the OP) or options after positional arguments (also requested by the OP); and it ignores parsing-errors. Instead:

  • Use enhanced getopt from util-linux or formerly GNU glibc.1
  • It works with getopt_long() the C function of GNU glibc.
  • no other solution on this page can do all this:
    • handles spaces, quoting characters and even binary in arguments2 (non-enhanced getopt can’t do this)
    • it can handle options at the end: script.sh -o outFile file1 file2 -v (getopts doesn’t do this)
    • allows =-style long options: script.sh --outfile=fileOut --infile fileIn (allowing both is lengthy if self parsing)
    • allows combined short options, e.g. -vfd (real work if self parsing)
    • allows touching option-arguments, e.g. -oOutfile or -vfdoOutfile
  • Is so old already3 that no GNU system is missing this (e.g. any Linux has it).
  • You can test for its existence with: getopt --test → return value 4.
  • Other getopt or shell-builtin getopts are of limited use.

The following calls

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFilemyscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFilemyscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFilemyscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfdmyscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

all return

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

with the following myscript

#!/bin/bash# More safety, by turning some bugs into errors.# Without `errexit` you don’t need ! and can replace# PIPESTATUS with a simple $?, but I don’t do that.set -o errexit -o pipefail -o noclobber -o nounset# -allow a command to fail with !’s side effect on errexit# -use return value from ${PIPESTATUS[0]}, because ! hosed $?! getopt --test > /dev/null if [[ ${PIPESTATUS[0]} -ne 4 ]]; then    echo 'I’m sorry, `getopt --test` failed in this environment.'    exit 1fiOPTIONS=dfo:vLONGOPTS=debug,force,output:,verbose# -regarding ! and PIPESTATUS see above# -temporarily store output to be able to check for errors# -activate quoting/enhanced mode (e.g. by writing out “--options”)# -pass arguments only via   -- "$@"   to separate them correctly! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")if [[ ${PIPESTATUS[0]} -ne 0 ]]; then    # e.g. return value is 1    #  then getopt has complained about wrong arguments to stdout    exit 2fi# read getopt’s output this way to handle the quoting right:eval set -- "$PARSED"d=n f=n v=n outFile=-# now enjoy the options in order and nicely split until we see --while true; do    case "$1" in        -d|--debug)            d=y            shift            ;;        -f|--force)            f=y            shift            ;;        -v|--verbose)            v=y            shift            ;;        -o|--output)            outFile="$2"            shift 2            ;;        --)            shift            break            ;;        *)            echo "Programming error"            exit 3            ;;    esacdone# handle non-option argumentsif [[ $# -ne 1 ]]; then    echo "$0: A single input file is required."    exit 4fiecho "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

1 enhanced getopt is available on most “bash-systems”, including Cygwin; on OS X try brew install gnu-getopt or sudo port install getopt
2 the POSIX exec() conventions have no reliable way to pass binary NULL in command line arguments; those bytes prematurely end the argument
3 first version released in 1997 or before (I only tracked it back to 1997)


deploy.sh

#!/bin/bashwhile [[ "$#" -gt 0 ]]; do    case $1 in        -t|--target) target="$2"; shift ;;        -u|--uglify) uglify=1 ;;        *) echo "Unknown parameter passed: $1"; exit 1 ;;    esac    shiftdoneecho "Where to deploy: $target"echo "Should uglify  : $uglify"

Usage:

./deploy.sh -t dev -u# OR:./deploy.sh --target dev --uglify