7

I am currently writing a BASH script in which I would like to use both positional and optional arguments. The positional arguments must occur at the location specified (ex: $1, $2) while the optional arguments can occur at any position denoted by their command line flag. Here is my script:

#!/usr/bin/env bash

usage() {
cat << EOF
Usage: progam ACTION NAME -k KEY_NAME
    ACTION ...... The program action to initiate
    NAME ........ The name of the object to create
    KEY_NAME .... The key name to use
EOF
}

ACTION=$1
NAME=$2
KEY_NAME=""

while getopts "k:" opt; do
    case $opt in
        k) KEY_NAME=$OPTARG; ;;
        [?]) usage && exit 1;
    esac
done

if [[ ! $ACTION ]]; then
    echo "Please select an action."
    exit 1
fi

if [[ ! $NAME ]]; then
    echo "Please include a name for the object."
    exit 1
fi

if [[ "$KEY_NAME" != "" ]]; then
    python3 -m program $ACTION -k $KEY_NAME -n $NAME
else
    echo "Please include a key name."
    exit 1
fi

exit 0

to run the program I would expect to be able to do the following:

fun_bash [action] [name] -k [key_name]

Where the things in brackets would be replaced by actual strings. When I execute, I always hit the condition that the key name does not exist:

Please include a key name.

How can I include mandatory positional arguments and optional command line flags within the same script?

5
  • 3
    Don't use getopts. If you want to allow this convention, use a library (like GNU getopt) that supports it. Commented Dec 17, 2019 at 16:30
  • 1
    getopts never sees -k, because it has a non-zero exit status when it sees its first non-optional argument. Commented Dec 17, 2019 at 16:30
  • @chepner I will look into gnu getopt Commented Dec 17, 2019 at 16:33
  • It isn't a lot of lines of script to implement a getopts equivalent that can even handle long opts. Commented Dec 17, 2019 at 16:35
  • I guess the issue is that I didn’t know you could do that gem. I will possibly look into that as well. Commented Dec 17, 2019 at 16:37

1 Answer 1

4

You may be be able to use a work-around in your current script as:

#!/usr/bin/env bash    
usage() {
cat << EOF
Usage: progam ACTION NAME -k KEY_NAME
    ACTION ...... The program action to initiate
    NAME ........ The name of the object to create
    KEY_NAME .... The key name to use
EOF
}

action="$1"
name="$2"
shift 2

key_name=""    
while getopts "k:" opt || :; do
    case $opt in
        k) key_name=$OPTARG; break ;;
        [?]) shift;;
    esac
done    
if [[ -z $action ]]; then
    echo "Please select an action."
    exit 1
fi    
if [[ -z $name ]]; then
    echo "Please include a name for the object."
    exit 1
fi    
if [[ -n $key_name ]]; then
    python3 -m program $action -k $key_name -n $name
else
    echo "Please include a key name."
    exit 1
fi    

Key difference is use of shift 2 before while loop that shifts argument by 2 positions since your first 2 arguments are fixed.

Also note use of [?]) shift;; inside getopts loop which shifts an argument every time it doesn't meet known options i.e. -k.

Also note that you should avoid using all uppercase variable names in your script to avoid overriding a builtin shell variable.

With these changes all the following command lines are accepted:

fun_bash arg1 arg2 -k mykey
fun_bash arg1 arg2 arg3 arg4 -k mykey
fun_bash arg1 arg2 arg3 arg4 arg5 -k mykey
Sign up to request clarification or add additional context in comments.

2 Comments

It is certainly an option, or to put the positional items last and deal with them after getopt has "failed". There are command-line tools that follow both these conventions.
This addresses everything I asked and more! Thanks for the thorough answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.