How to get arguments with flags in Bash

I know that I can easily get positioned parameters like this in bash:

$0 or $1

I want to be able to use flag options like this to specify for what each parameter is used:

mysql -u user -h host 

What is the best way to get -u param value and -h param value by flag instead of by position?

4

11 Answers

This example uses Bash's built-in getopts command and is from the Google Shell Style Guide:

a_flag='' b_flag='' files='' verbose='false' print_usage() { printf "Usage: ..." } while getopts 'abf:v' flag; do case "${flag}" in a) a_flag='true' ;; b) b_flag='true' ;; f) files="${OPTARG}" ;; v) verbose='true' ;; *) print_usage exit 1 ;; esac done 

Note: If a character is followed by a colon (e.g. f:), that option is expected to have an argument.

Example usage: ./script -v -a -b -f filename

Using getopts has several advantages over the accepted answer:

  • the while condition is a lot more readable and shows what the accepted options are
  • cleaner code; no counting the number of parameters and shifting
  • you can join options (e.g. -a -b -c-abc)

However, a big disadvantage is that it doesn't support long options, only single-character options.

16

This is the idiom I usually use:

while test $# -gt 0; do case "$1" in -h|--help) echo "$package - attempt to capture frames" echo " " echo "$package [options] application [arguments]" echo " " echo "options:" echo "-h, --help show brief help" echo "-a, --action=ACTION specify an action to use" echo "-o, --output-dir=DIR specify a directory to store output in" exit 0 ;; -a) shift if test $# -gt 0; then export PROCESS=$1 else echo "no process specified" exit 1 fi shift ;; --action*) export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'` shift ;; -o) shift if test $# -gt 0; then export OUTPUT=$1 else echo "no output dir specified" exit 1 fi shift ;; --output-dir*) export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'` shift ;; *) break ;; esac done 

Key points are:

  • $# is the number of arguments
  • while loop looks at all the arguments supplied, matching on their values inside a case statement
  • shift takes the first one away. You can shift multiple times inside of a case statement to take multiple values.
14

getopt is your friend.. a simple example:

function f () { TEMP=`getopt --long -o "u:h:" "$@"` eval set -- "$TEMP" while true ; do case "$1" in -u ) user=$2 shift 2 ;; -h ) host=$2 shift 2 ;; *) break ;; esac done; echo "user = $user, host = $host" } f -u myself -h some_host 

There should be various examples in your /usr/bin directory.

3

I propose a simple TLDR:; example for the un-initiated.

Create a bash script called greeter.sh

#!/bin/bash while getopts "n:" arg; do case $arg in n) Name=$OPTARG;; esac done echo "Hello $Name!" 

You can then pass an optional parameter -n when executing the script.

Execute the script as such:

$ bash greeter.sh -n 'Bob' 

Output

$ Hello Bob! 

Notes

If you'd like to use multiple parameters:

  1. extend while getops "n:" arg: do with more paramaters such as while getops "n:o:p:" arg: do
  2. extend the case switch with extra variable assignments. Such as o) Option=$OPTARG and p) Parameter=$OPTARG

To make the script executable:

chmod u+x greeter.sh 

I think this would serve as a simpler example of what you want to achieve. There is no need to use external tools. Bash built in tools can do the job for you.

function DOSOMETHING { while test $# -gt 0; do case "$1" in -first) shift first_argument=$1 shift ;; -last) shift last_argument=$1 shift ;; *) echo "$1 is not a recognized flag!" return 1; ;; esac done echo "First argument : $first_argument"; echo "Last argument : $last_argument"; } 

This will allow you to use flags so no matter which order you are passing the parameters you will get the proper behavior.

Example :

 DOSOMETHING -last "Adios" -first "Hola" 

Output :

 First argument : Hola Last argument : Adios 

You can add this function to your profile or put it inside of a script.

Thanks!

Edit : Save this as a a file and then execute it as yourfile.sh -last "Adios" -first "Hola"

#!/bin/bash while test $# -gt 0; do case "$1" in -first) shift first_argument=$1 shift ;; -last) shift last_argument=$1 shift ;; *) echo "$1 is not a recognized flag!" return 1; ;; esac done echo "First argument : $first_argument"; echo "Last argument : $last_argument"; 
6

Another alternative would be to use something like the below example which would allow you to use long --image or short -i tags and also allow compiled -i="example.jpg" or separate -i example.jpg methods of passing in arguments.

# declaring a couple of associative arrays declare -A arguments=(); declare -A variables=(); # declaring an index integer declare -i index=1; # any variables you want to use here # on the left left side is argument label or key (entered at the command line along with it's value) # on the right side is the variable name the value of these arguments should be mapped to. # (the examples above show how these are being passed into this script) variables["-gu"]="git_user"; variables["--git-user"]="git_user"; variables["-gb"]="git_branch"; variables["--git-branch"]="git_branch"; variables["-dbr"]="db_fqdn"; variables["--db-redirect"]="db_fqdn"; variables["-e"]="environment"; variables["--environment"]="environment"; # $@ here represents all arguments passed in for i in "$@" do arguments[$index]=$i; prev_index="$(expr $index - 1)"; # this if block does something akin to "where $i contains =" # "%=*" here strips out everything from the = to the end of the argument leaving only the label if [[ $i == *"="* ]] then argument_label=${i%=*} else argument_label=${arguments[$prev_index]} fi # this if block only evaluates to true if the argument label exists in the variables array if [[ -n ${variables[$argument_label]} ]] then # dynamically creating variables names using declare # "#$argument_label=" here strips out the label leaving only the value if [[ $i == *"="* ]] then declare ${variables[$argument_label]}=${i#$argument_label=} else declare ${variables[$argument_label]}=${arguments[$index]} fi fi index=index+1; done; # then you could simply use the variables like so: echo "$git_user"; 

I like Robert McMahan's answer the best here as it seems the easiest to make into sharable include files for any of your scripts to use. But it seems to have a flaw with the line if [[ -n ${variables[$argument_label]} ]] throwing the message, "variables: bad array subscript". I don't have the rep to comment, and I doubt this is the proper 'fix,' but wrapping that if in if [[ -n $argument_label ]] ; then cleans it up.

Here's the code I ended up with, if you know a better way please add a comment to Robert's answer.

Include File "flags-declares.sh"

# declaring a couple of associative arrays declare -A arguments=(); declare -A variables=(); # declaring an index integer declare -i index=1; 

Include File "flags-arguments.sh"

# $@ here represents all arguments passed in for i in "$@" do arguments[$index]=$i; prev_index="$(expr $index - 1)"; # this if block does something akin to "where $i contains =" # "%=*" here strips out everything from the = to the end of the argument leaving only the label if [[ $i == *"="* ]] then argument_label=${i%=*} else argument_label=${arguments[$prev_index]} fi if [[ -n $argument_label ]] ; then # this if block only evaluates to true if the argument label exists in the variables array if [[ -n ${variables[$argument_label]} ]] ; then # dynamically creating variables names using declare # "#$argument_label=" here strips out the label leaving only the value if [[ $i == *"="* ]] then declare ${variables[$argument_label]}=${i#$argument_label=} else declare ${variables[$argument_label]}=${arguments[$index]} fi fi fi index=index+1; done; 

Your "script.sh"

. bin/includes/flags-declares.sh # any variables you want to use here # on the left left side is argument label or key (entered at the command line along with it's value) # on the right side is the variable name the value of these arguments should be mapped to. # (the examples above show how these are being passed into this script) variables["-gu"]="git_user"; variables["--git-user"]="git_user"; variables["-gb"]="git_branch"; variables["--git-branch"]="git_branch"; variables["-dbr"]="db_fqdn"; variables["--db-redirect"]="db_fqdn"; variables["-e"]="environment"; variables["--environment"]="environment"; . bin/includes/flags-arguments.sh # then you could simply use the variables like so: echo "$git_user"; echo "$git_branch"; echo "$db_fqdn"; echo "$environment"; 

If you're familiar with Python argparse, and don't mind calling python to parse bash arguments, there is a piece of code I found really helpful and super easy to use called argparse-bash

Example take from their example.sh script:

#!/bin/bash source $(dirname $0)/argparse.bash || exit 1 argparse "$@" <<EOF || exit 1 parser.add_argument('infile') parser.add_argument('outfile') parser.add_argument('-a', '--the-answer', default=42, type=int, help='Pick a number [default %(default)s]') parser.add_argument('-d', '--do-the-thing', action='store_true', default=False, help='store a boolean [default %(default)s]') parser.add_argument('-m', '--multiple', nargs='+', help='multiple values allowed') EOF echo required infile: "$INFILE" echo required outfile: "$OUTFILE" echo the answer: "$THE_ANSWER" echo -n do the thing? if [[ $DO_THE_THING ]]; then echo " yes, do it" else echo " no, do not do it" fi echo -n "arg with multiple values: " for a in "${MULTIPLE[@]}"; do echo -n "[$a] " done echo 
#!/bin/bash if getopts "n:" arg; then echo "Welcome $OPTARG" fi 

Save it as sample.sh and try running

sh sample.sh -n John 

in your terminal.

I had trouble using getopts with multiple flags, so I wrote this code. It uses a modal variable to detect flags, and to use those flags to assign arguments to variables.

Note that, if a flag shouldn't have an argument, something other than setting CURRENTFLAG can be done.

 for MYFIELD in "$@"; do CHECKFIRST=`echo $MYFIELD | cut -c1` if [ "$CHECKFIRST" == "-" ]; then mode="flag" else mode="arg" fi if [ "$mode" == "flag" ]; then case $MYFIELD in -a) CURRENTFLAG="VARIABLE_A" ;; -b) CURRENTFLAG="VARIABLE_B" ;; -c) CURRENTFLAG="VARIABLE_C" ;; esac elif [ "$mode" == "arg" ]; then case $CURRENTFLAG in VARIABLE_A) VARIABLE_A="$MYFIELD" ;; VARIABLE_B) VARIABLE_B="$MYFIELD" ;; VARIABLE_C) VARIABLE_C="$MYFIELD" ;; esac fi done 

So here it is my solution. I wanted to be able to handle boolean flags without hyphen, with one hyphen, and with two hyphen as well as parameter/value assignment with one and two hyphens.

# Handle multiple types of arguments and prints some variables # # Boolean flags # 1) No hyphen # create Assigns `true` to the variable `CREATE`. # Default is `CREATE_DEFAULT`. # delete Assigns true to the variable `DELETE`. # Default is `DELETE_DEFAULT`. # 2) One hyphen # a Assigns `true` to a. Default is `false`. # b Assigns `true` to b. Default is `false`. # 3) Two hyphens # cats Assigns `true` to `cats`. By default is not set. # dogs Assigns `true` to `cats`. By default is not set. # # Parameter - Value # 1) One hyphen # c Assign any value you want # d Assign any value you want # # 2) Two hyphens # ... Anything really, whatever two-hyphen argument is given that is not # defined as flag, will be defined with the next argument after it. # # Example: # ./parser_example.sh delete -a -c VA_1 --cats --dir /path/to/dir parser() { # Define arguments with one hyphen that are boolean flags HYPHEN_FLAGS="a b" # Define arguments with two hyphens that are boolean flags DHYPHEN_FLAGS="cats dogs" # Iterate over all the arguments while [ $# -gt 0 ]; do # Handle the arguments with no hyphen if [[ $1 != "-"* ]]; then echo "Argument with no hyphen!" echo $1 # Assign true to argument $1 declare $1=true # Shift arguments by one to the left shift # Handle the arguments with one hyphen elif [[ $1 == "-"[A-Za-z0-9]* ]]; then # Handle the flags if [[ $HYPHEN_FLAGS == *"${1/-/}"* ]]; then echo "Argument with one hyphen flag!" echo $1 # Remove the hyphen from $1 local param="${1/-/}" # Assign true to $param declare $param=true # Shift by one shift # Handle the parameter-value cases else echo "Argument with one hyphen value!" echo $1 $2 # Remove the hyphen from $1 local param="${1/-/}" # Assign argument $2 to $param declare $param="$2" # Shift by two shift 2 fi # Handle the arguments with two hyphens elif [[ $1 == "--"[A-Za-z0-9]* ]]; then # NOTE: For double hyphen I am using `declare -g $param`. # This is the case because I am assuming that's going to be # the final name of the variable echo "Argument with two hypens!" # Handle the flags if [[ $DHYPHEN_FLAGS == *"${1/--/}"* ]]; then echo $1 true # Remove the hyphens from $1 local param="${1/--/}" # Assign argument $2 to $param declare -g $param=true # Shift by two shift # Handle the parameter-value cases else echo $1 $2 # Remove the hyphens from $1 local param="${1/--/}" # Assign argument $2 to $param declare -g $param="$2" # Shift by two shift 2 fi fi done # Default value for arguments with no hypheb CREATE=${create:-'CREATE_DEFAULT'} DELETE=${delete:-'DELETE_DEFAULT'} # Default value for arguments with one hypen flag VAR1=${a:-false} VAR2=${b:-false} # Default value for arguments with value # NOTE1: This is just for illustration in one line. We can well create # another function to handle this. Here I am handling the cases where # we have a full named argument and a contraction of it. # For example `--arg1` can be also set with `-c`. # NOTE2: What we are doing here is to check if $arg is defined. If not, # check if $c was defined. If not, assign the default value "VD_" VAR3=$(if [[ $arg1 ]]; then echo $arg1; else echo ${c:-"VD_1"}; fi) VAR4=$(if [[ $arg2 ]]; then echo $arg2; else echo ${d:-"VD_2"}; fi) } # Pass all the arguments given to the script to the parser function parser "$@" echo $CREATE $DELETE $VAR1 $VAR2 $VAR3 $VAR4 $cats $dir 

Some references

  • The main procedure was found here.
  • More about passing all the arguments to a function here.
  • More info regarding default values here.
  • More info about declare do $ bash -c "help declare".
  • More info about shift do $ bash -c "help shift".

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like