How do I parse command line arguments in Bash?

ID : 348

viewed : 147

Tags : bashcommand-linescriptingargumentsgetoptsbash

Top 5 Answer for How do I parse command line arguments in Bash?

vote vote

95

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

cat >/tmp/demo-space-separated.sh <<'EOF' #!/bin/bash  POSITIONAL=() 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       ;;   esac done  set -- "${POSITIONAL[@]}" # restore positional parameters  echo "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" fi EOF  chmod +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  = conf SEARCH PATH     = /etc LIBRARY PATH    = /usr/lib DEFAULT         = Number files in SEARCH PATH with EXTENSION: 14 Last 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/bash  for 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       ;;   esac done echo "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 fi EOF  chmod +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  = conf SEARCH PATH     = /etc LIBRARY PATH    = /usr/lib DEFAULT         = Number files in SEARCH PATH with EXTENSION: 14 Last 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 variable OPTIND=1         # Reset in case getopts has been used previously in the shell.  # Initialize our own variables: output_file="" verbose=0  while getopts "h?vf:" opt; do   case "$opt" in     h|\?)       show_help       exit 0       ;;     v)  verbose=1       ;;     f)  output_file=$OPTARG       ;;   esac done  shift $((OPTIND-1))  [ "${1:-}" = "--" ] && shift  echo "verbose=$verbose, output_file='$output_file', Leftovers: $@" EOF  chmod +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.

vote vote

89

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/someOtherFile myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd myscript ./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 1 fi  OPTIONS=dfo:v LONGOPTS=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 2 fi # 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             ;;     esac done  # handle non-option arguments if [[ $# -ne 1 ]]; then     echo "$0: A single input file is required."     exit 4 fi  echo "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)

vote vote

72

deploy.sh

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

Usage:

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

61

From digitalpeer.com with minor modifications:

Usage myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash for i in "$@" do case $i in     -p=*|--prefix=*)     PREFIX="${i#*=}"      ;;     -s=*|--searchpath=*)     SEARCHPATH="${i#*=}"     ;;     -l=*|--lib=*)     DIR="${i#*=}"     ;;     --default)     DEFAULT=YES     ;;     *)             # unknown option     ;; esac done echo PREFIX = ${PREFIX} echo SEARCH PATH = ${SEARCHPATH} echo DIRS = ${DIR} echo DEFAULT = ${DEFAULT} 

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.

vote vote

55

while [ "$#" -gt 0 ]; do   case "$1" in     -n) name="$2"; shift 2;;     -p) pidfile="$2"; shift 2;;     -l) logfile="$2"; shift 2;;      --name=*) name="${1#*=}"; shift 1;;     --pidfile=*) pidfile="${1#*=}"; shift 1;;     --logfile=*) logfile="${1#*=}"; shift 1;;     --name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;          -*) echo "unknown option: $1" >&2; exit 1;;     *) handle_argument "$1"; shift 1;;   esac done 

This solution:

  • handles -n arg and --name=arg
  • allows arguments at the end
  • shows sane errors if anything is misspelled
  • compatible, doesn't use bashisms
  • readable, doesn't require maintaining state in a loop

Top 3 video Explaining How do I parse command line arguments in Bash?

Related QUESTION?