Villi Magg
Villi Magg

Reputation: 1193

BASH getopts optional arguments

I'm trying to create a shell script that has two mandatory arguments (arg1, arg2) and then an optional -b flag plus the argument that needs to follow should the user choose to use the flag.

Let me explain:

It's an install script that takes an advantage of GIT to fetch an application from a Github repository. The user types in terminal f.ex.:

./shellscript.sh new <app_name> # fetches master branch by default

and the script fetches an instance of the master branch from this repository. Should the user however choose to use the optional -b flag that would mean that he/she wants to specify which branch to fetch, e.g. develop branch. Meaning that the user could do:

./shellscript.sh new <app_name> -b develop # or which ever branch there is available

I'm also curious how you could go about making the script work so that it wouldn't matter if the user types in the -b flag+branch_name before the 'new' argument and the 'app_name' argument. But that is perhaps not the most important thing at the moment.

To know what exactly I'm trying to build, here is a link to my current script that only takes the two mandatory arguments and only fetches the master branch: My Super Cool Artisan Executable Script

P.S.: I've been trying out many examples using getopts which I've found both here on Stackoverflow and other blogs out there but none have helped me to completely make it work. Thus am I here asking y'all great people for help.

Huge thanks and be sure to check out my Linux Mint / Ubuntu - Post Install Script for only cool people(you guys and those switching over to Linux from Windows/Mac)

Regards, Villi

Upvotes: 2

Views: 11216

Answers (3)

TrueY
TrueY

Reputation: 7610

A little bit simplified version, using that getopts can report errors:

#!/usr/bin/bash

help() { echo -e "Usage\n\t$0: new <app_name> [-b <develop>]" >&2;}
die() { [ -n "$1" ] && echo -e "Error: $1\n" >&2; help; [ -z "$1" ]; exit;}

[ $# -lt 2 ] && die "Too few args"
[ $1 != "new" ] && die "Bad first arg ($1)"
app_name=$2
shift 2

unset develop
while getopts "b:" opt; do
  case $opt in
    \?) exit 1;;
    b) develop="$OPTARG";;
  esac
done

echo "app_name: $app_name, develop: $develop"

Test:

$./test new App
app_name: App, develop:
$./test new App -b Dev
app_name: App, develop: Dev

Anyway I may suggest to use to use the standard way of argument passing. Instead of new you may use -n.

Upvotes: 1

rici
rici

Reputation: 241671

Unix utilities normally take optional arguments ("flags") before the positional arguments, although most GNU utilities, including the GNU implementation of the C library function getopt, shuffle command line arguments so that the optional arguments come first. However, the bash builtin getopts does not shuffle, which means that it is up to you to do so if you want to.

getopts always starts with the argument whose number is the value of the variable OPTIND. (OPTIND is set to 1 every time a bash function is executed, and it's a global variable. So a bit of caution is needed with bash functions which call each other.) If you want to, you can set OPTIND yourself, and the next call to getopts will start with that index. Alternatively, you can use shift to shift all the command line arguments over.

So, for example, you could do this:

# "shift" version: always shift consumed arguments
local verb="$1" branch=master app_name option
shift
case $verb in
  new)  app_name="$1"
        shift
        while getopts b: option; do
          case $option in
            b) branch=$OPTARG;;
            *) # handle the error;;
          esac
        done
        shift $((OPTIND - 1));;
  *) # handle the error or other subcommands;;
esac
# At this point, there are still arguments if ((OPTIND > 0))

Or:

# non-shift version: use OPTIND to index arguments
local verb="${!OPTIND}" branch=master app_name option
OPTIND=$((OPTIND + 1))
case $verb in
  new)  app_name="${!OPTIND}"
        OPTIND=$((OPTIND + 1))
        while getopts b: option; do
          case $option in
            b) branch=$OPTARG;;
            *) # handle the error;;
          esac
        done;;
  *) # handle the error or other subcommands;;
esac
# At this point, there are still arguments if ((OPTIND > $#))

Upvotes: 2

cforbish
cforbish

Reputation: 8819

I normally write my own to allow for short and long options:

function Usage()
{
cat <<-ENDOFMESSAGE

$0 [OPTION] REQ1 REQ2

options:

    -b -branch  branch to use
    -h --help   display this message

ENDOFMESSAGE
    exit 1
}

function Die()
{
    echo "$*"
    exit 1
}

function GetOpts() {
    branch=""
    argv=()
    while [ $# -gt 0 ]
    do
        opt=$1
        shift
        case ${opt} in
            -b|--branch)
                if [ $# -eq 0 -o "${1:0:1}" = "-" ]; then
                    Die "The ${opt} option requires an argument."
                fi
                branch="$1"
                shift
                ;;
            -h|--help)
                Usage;;
            *)
                if [ "${opt:0:1}" = "-" ]; then
                    Die "${opt}: unknown option."
                fi
                argv+=(${opt});;
        esac
    done 
}

GetOpts $*
echo "branch ${branch}"
echo "argv ${argv[@]}"

Upvotes: 7

Related Questions