71

I am trying to parse a -temp option with Bash getopts. I'm calling my script like this:

./myscript -temp /foo/bar/someFile 

Here is the code I'm using to parse the options.

while getopts "temp:shots:o:" option; do case $option in temp) TMPDIR="$OPTARG" ;; shots) NUMSHOTS="$OPTARG" ;; o) OUTFILE="$OPTARG" ;; *) usage ;; esac done shift $(($OPTIND - 1)) [ $# -lt 1 ] && usage 
2
  • Possible duplicate of How do I parse command line arguments in Bash? Commented Oct 17, 2018 at 16:02
  • 6
    I disagree of it being duplicate. That question is more general how to parse, while this one is more specific as how to parse long options in perticular. Commented May 6, 2020 at 19:24

8 Answers 8

125

As other people explained, getopts doesn't parse long options. You can use getopt, but it's not portable (and it is broken on some platform...)

As a workaround, you can implement a shell loop. Here an example that transforms long options to short ones before using the standard getopts command (it's simpler in my opinion):

# Transform long options to short ones for arg in "$@"; do shift case "$arg" in '--help') set -- "$@" '-h' ;; '--number') set -- "$@" '-n' ;; '--rest') set -- "$@" '-r' ;; '--ws') set -- "$@" '-w' ;; *) set -- "$@" "$arg" ;; esac done # Default behavior number=0; rest=false; ws=false # Parse short options OPTIND=1 while getopts "hn:rw" opt do case "$opt" in 'h') print_usage; exit 0 ;; 'n') number=$OPTARG ;; 'r') rest=true ;; 'w') ws=true ;; '?') print_usage >&2; exit 1 ;; esac done shift $(expr $OPTIND - 1) # remove options from positional parameters 
Sign up to request clarification or add additional context in comments.

15 Comments

This is great, except that it fails to notify the user properly if an invalid argument is passed, instead stating illegal option -- -. I added the following to my script to help: "--"*) usage ${arg}; exit 2;;
It may be a good idea. My example can be improved in the same way to allow a syntax like "--separator=STRING". I chose to implement a simple loop and do not allow all possible syntaxes. It can be improved.
Works as a charm. Thank you for sharing your solution.
Please extend with a long option that requires an argument.
@dotnetCarpenter shift $(expr $OPTIND - 1) is useful when your command has other arguments after positional parameters, typically what is after -- on the command line.
|
59

getopts can only parse short options.

Most systems also have an external getopt command, but getopt is not standard, and is generally broken by design as it can't handle all arguments safely (arguments with whitespace and empty arguments), only GNU getopt can handle them safely, but only if you use it in a GNU-specific way.

The easier choice is to use neither, just iterate the script's arguments with a while-loop and do the parsing yourself.

See http://mywiki.wooledge.org/BashFAQ/035 for an example.

1 Comment

No need to reinvent the wheel here, just use getopts and look-up other ways to parse long arguments, or trick it like the @mcoolive shows here
6

the simplest way to achieve this is with the help of getopt and --longoptions

try this , hope this is useful

# Read command line options ARGUMENT_LIST=( "input1" "input2" "input3" ) # read arguments opts=$(getopt \ --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \ --name "$(basename "$0")" \ --options "" \ -- "$@" ) echo $opts eval set --$opts while true; do case "$1" in --input1) shift empId=$1 ;; --input2) shift fromDate=$1 ;; --input3) shift toDate=$1 ;; --) shift break ;; esac shift done 

and this is how - you call the shell script

myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ" 

Comments

3

getopts is used by shell procedures to parse positional parameters of 1 character only (no GNU-style long options (--myoption) or XF86-style long options (-myoption))

Comments

1

It's true that builtin bash getopts only parse short options, but you can still add few lines of scripting to make getopts handles long options.

Here is a part of code found in http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

 #== set options ==# SCRIPT_OPTS=':fbF:B:-:h' typeset -A ARRAY_OPTS ARRAY_OPTS=( [foo]=f [bar]=b [foobar]=F [barfoo]=B [help]=h [man]=h ) #== parse options ==# while getopts ${SCRIPT_OPTS} OPTION ; do #== translate long options to short ==# if [[ "x$OPTION" == "x-" ]]; then LONG_OPTION=$OPTARG LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2) LONG_OPTIND=-1 [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1) [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND" OPTION=${ARRAY_OPTS[$LONG_OPTION]} [[ "x$OPTION" = "x" ]] && OPTION="?" OPTARG="-$LONG_OPTION" if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then OPTION=":" OPTARG="-$LONG_OPTION" else OPTARG="$LONG_OPTARG"; if [[ $LONG_OPTIND -ne -1 ]]; then [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 )) shift $OPTIND OPTIND=1 fi fi fi fi #== options follow by another option instead of argument ==# if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then OPTARG="$OPTION" OPTION=":" fi #== manage options ==# case "$OPTION" in f ) foo=1 bar=0 ;; b ) foo=0 bar=1 ;; B ) barfoo=${OPTARG} ;; F ) foobar=1 && foobar_name=${OPTARG} ;; h ) usagefull && exit 0 ;; : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;; ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;; esac done shift $((${OPTIND} - 1)) 

Here is a test:

# Short options test $ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2 foo=0 bar=1 barfoo=6 foobar=1 foobar_name=Hello world files=file1 file2 # Long and short options test $ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2 foo=0 bar=1 barfoo=6 foobar=1 foobar_name=Hello files=file1 file2 

Otherwise in recent Korn Shell ksh93, getopts can naturally parse long options and even display a man page alike. (see http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options)

Michel VONGVILAY.

1 Comment

Nice one, thank you. Unfortunately, it breaks if an argument contains '=' or an argument is an empty string. Should I suggest a version that supports '=' and empty strings as arguments?
1

Although this question was posted over 2 years ago, I found myself needing support for XFree86-style long options too; and I also wanted to take what I could from getopts. Consider the GCC switch -rdynamic. I mark r as the flag letter, and expect dynamic within $OPTARG...but, I want to reject -r dynamic, while accepting other options following r.

The idea I've put below builds on the observation that $OPTIND will be one larger than otherwise if space (a gap) follows the flag. So, I define a bash variable to hold the previous value of $OPTIND, called $PREVOPTIND, and update it at the end of the while loop. If $OPTIND is 1 greater than $PREVOPTIND, we have no gap (i.e. -rdynamic); and $GAP is set to false. If instead $OPTIND is 2 greater than $PREVOPTIND, we do have a gap (e.g. -r dynamic), and $GAP is set to true.

usage() { echo usage: error from $1; exit -1; } OPTIND=1 PREVOPTIND=$OPTIND while getopts "t:s:o:" option; do GAP=$((OPTIND-(PREVOPTIND+1))) case $option in t) case "${OPTARG}" in emp) # i.e. -temp ((GAP)) && usage "-${option} and ${OPTARG}" TMPDIR="$OPTARG" ;; *) true ;; esac ;; s) case "${OPTARG}" in hots) # i.e. -shots ((GAP)) && usage NUMSHOTS="$OPTARG" ;; *) usage "-${option} and ${OPTARG}" ;; esac ;; o) OUTFILE="$OPTARG" ;; *) usage "-${option} and ${OPTARG}" ;; esac PREVOPTIND=$OPTIND done shift $(($OPTIND - 1)) 

Comments

0

You can't use the getopts Bash builtin for long options--at least, not without having to build your own parsing functions. You should take a look at the /usr/bin/getopt binary instead (provided on my system by the util-linux package; your mileage may vary).

See getopt(1) for specific invocation options.

1 Comment

I don't think /usr/bin/getopt supports long options either.
0

thanks to @mcoolive .

I was able to use your $@ idea to convert whole word and long options to single letter options. Wanted to note to anyone using this idea that I also had to include shift $(expr $OPTIND - 1) prior to running the arguments through the getopts loop.

Totally different purpose but this is working well.

# convert long word options to short word for ease of use and portability for argu in "$@"; do shift #echo "curr arg = $1" case "$argu" in "-start"|"--start") # param=param because no arg is required set -- "$@" "-s" ;; "-pb"|"--pb"|"-personalbrokers"|"--personalbrokers") # pb +arg required set -- "$@" "-p $1"; #echo "arg=$argu" ;; "-stop"|"--stop") # param=param because no arg is required set -- "$@" "-S" ;; # the catch all option here removes all - symbols from an # argument. if an option is attempted to be passed that is # invalid, getopts knows what to do... *) [[ $(echo $argu | grep -E "^-") ]] && set -- "$@" "${argu//-/}" || echo "no - symbol. not touching $argu" &>/dev/null ;; esac done #echo -e "\n final option conversions = $@\n" # remove options from positional parameters for getopts parsing shift $(expr $OPTIND - 1) declare -i runscript=0 # only p requires an argument hence the p: while getopts "sSp:" param; do [[ "$param" == "p" ]] && [[ $(echo $OPTARG | grep -E "^-") ]] && funcUsage "order" #echo $param #echo "OPTIND=$OPTIND" case $param in s) OPTARG=${OPTARG/ /} getoptsRan=1 echo "$param was passed and this is it's arg $OPTARG" arg0=start ;; p) OPTARG=${OPTARG/ /} getoptsRan=1 echo "$param was passed and this is it's arg $OPTARG" [[ "$OPTARG" == "all" ]] && echo -e "argument \"$OPTARG\" accepted. continuing." && (( runscript += 1 )) || usage="open" [[ $( echo $pbString | grep -w "$OPTARG" ) ]] && echo -e "pb $OPTARG was validated. continuing.\n" && (( runscript += 1 )) || usage="personal" [[ "$runscript" -lt "1" ]] && funcUsage "$usage" "$OPTARG" arg0=start ;; S) OPTARG=${OPTARG/ /} getoptsRan=1 echo "$param was passed and this is it's arg $OPTARG" arg0=stop ;; *) getoptsRan=1 funcUsage echo -e "Invalid argument\n" ;; esac done funcBuildExcludes "$@" shift $((OPTIND-1)) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.