How to Parse Command Line Options in Bash Shell

How do I parse command line arguments in Bash?


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

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL_ARGS=()

while [[ $# -gt 0 ]]; do
case $1 in
-e|--extension)
EXTENSION="$2"
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH="$2"
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
-*|--*)
echo "Unknown option $1"
exit 1
;;
*)
POSITIONAL_ARGS+=("$1") # save positional arg
shift # past argument
;;
esac
done

set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters

echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
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 /etc/hosts
Output from copy-pasting the block above
FILE EXTENSION  = conf
SEARCH PATH = /etc
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 /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
;;
--default)
DEFAULT=YES
shift # past argument with no value
;;
-*|--*)
echo "Unknown option $i"
exit 1
;;
*)
;;
esac
done

echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
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 /etc/hosts
Output from copy-pasting the block above
FILE EXTENSION  = conf
SEARCH PATH = /etc
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 /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.

What is the best way to parse command line options in bash shell?

Use shell built-in getopts or GNU command getopt.

Parsing command line arguments in a shell script function

Function can have arguments passed. so $@ inside function becomes functions args not the shell arguments at command line.

ALL_ARGS_PASSED="$@" is wrong $@ is already quoted args list. If you quote again it becomes single string and hence only first argument is parsed rest are the value.

""aa=1 bb=2 cc=3""
so if you parse this aa is key and value is "1 bb=2 cc=3"

so the solution is not to quote for ALL_ARGS_PASSED

ALL_PASSED_ARGS=$@

Argument parsing in bash


VMMOUNT=""
BOOTSTRAP=""
IMAGE_FILE=""
TARGET_EXE=""
INTERNAL_EXE=""
while : ; do
case "$1" in
--vmmount)
[ -n "${VMMOUNT}" ] && usage
VMMOUNT="$2"
shift 2 ;;
--bootstrap)
[ -n "${BOOTSTRAP}" ] && usage
BOOTSTRAP="$2"
shift 2 ;;
--image)
[ -n "${IMAGE_FILE}" ] && usage
IMAGE_FILE="$2"
shift 2 ;;
--target-exe)
[ -n "${TARGET_EXE}" ] && usage
TARGET_EXE="$2"
shift 2 ;;
--internal-exe)
[ -n "${INTERNAL_EXE}" ] && usage
INTERNAL_EXE="true"
shift ;;
*)
break ;;
esac
done
my_method "${IMAGE_FILE}" "${VMMOUNT}" "${BOOTSTRAP}" "${TARGET_EXE}" "${INTERNAL_EXE}" "$@"

Don't forget to enclose $@ in double quotes.

How Bash parse multi-flag commands?

The shell does not attempt to parse command arguments; that's the responsibility of the utility. The range of possible command argument syntaxes, both in use and potentially useful, is far too great to attempt that.

On Unix-like systems, the shell identifies individual arguments from the command line, mostly by splitting at whitespace but also taking into account the use of quotes and a variety of other transformations, such as "glob expansion". It then makes a vector of these arguments ("argv") and passes the vector to execve, which hands them to the newly created process.

On Windows systems, the shell doesn't even do that. It just hands over the command-line as a string, and leaves it to the command-line tool to do everything. (In order to provide a modicum of compatibility, there's an intermediate layer which is called by the application initialization code, which eventually calls main(). This does some basic argument-splitting, although its quoting algorithm is quite a bit simplified from that used by a Unix shell.)

No command-line shell that I know of attempts to identify command-line flags. And neither should you.

For a bit of extracurricular reading, here's the description of shell parsing from the Posix standard: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html. Trying to implement all that goes far beyond the requirements given to you for this assignment, and I'm certainly not recommending that you do that. But it might still be interesting, and understanding it will help you immensely if you start using a shell.

Alternatively, you could try reading the Bash manual, which might be easier to understand. Note that Bash implements a lot of extensions to the Posix standard.

Parsing shell script arguments

There are lots of ways to parse arguments in sh. Getopt is good. Here's a simple script that parses things by hand:

#!/bin/sh
# WARNING: see discussion and caveats below
# this is extremely fragile and insecure

while echo $1 | grep -q ^-; do
# Evaluating a user entered string!
# Red flags!!! Don't do this
eval $( echo $1 | sed 's/^-//' )=$2
shift
shift
done

echo host = $host
echo user = $user
echo pass = $pass
echo args = $@

A sample run looks like:

$ ./a.sh -host foo -user me -pass secret some args
host = foo
user = me
pass = secret
args = some args

Note that this is not even remotely robust and massively open to security
holes since the script eval's a string constructed by the user. It is merely
meant to serve as an example for one possible way to do things. A simpler method is to require the user to pass the data in the environment. In a bourne shell (ie, anything that is not in the csh family):

$ host=blah user=blah pass=blah myscript.sh

works nicely, and the variables $host, $user, $pass will be available in the script.

#!/bin/sh
echo host = ${host:?host empty or unset}
echo user = ${user?user not set}
...

Bash : Parse options after arguments with getopts

The getopt command (part of the util-linux package and different from getopts) will do what you want. The bash faq has some opinions about using that, but honestly these days most systems will have the modern version of getopt.

Consider the following example:

#!/bin/sh

options=$(getopt -o o: --long option: -- "$@")
eval set -- "$options"

while :; do
case "$1" in
-o|--option)
shift
OPTION=$1
;;
--)
shift
break
;;
esac

shift
done

echo "got option: $OPTION"
echo "remaining args are: $@"

We can call this like this:

$ ./options.sh -o foo arg1 arg2
got option: foo
remaining args are: arg1 arg2

Or like this:

$ ./options.sh  arg1 arg2 -o foo
got option: foo
remaining args are: arg1 arg2

Parse combination of command-line arguments and flags in bash

Using a while loop to read and shift the arguments might be easier in this case.
In the example below the arguments are looped through to look for the string -t in which case the arguments array is shifted one step and the now nr 1 index is supposed to be the optional homedir. In all the other cases the item is moved to another array called files.

#! /bin/bash

files=()
homedir=
while (( $# > 0 )); do
case "$1" in
-t )
shift
homedir="$1"
;;
* )
files+=("$1")
;;
esac
shift
done
echo "${files[@]}"
echo "$homedir"


Related Topics



Leave a reply



Submit