User Rating: 5 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Active
 

In older ksh or bash, the builtin command getopts can usually only handle short option.
But newer Korn shell may be able to handle long options as show in an other article

If you don't have the chance to get a recent shell, or if you want to write more portable script, this article will show how to handle GNU-style long options ( -- double hyphen) using getopts with few extra lines of scripting.

(foobar_any_getopts.sh zip file attachement in this article)

The script in this article has been tested ...
... on Oracle Linux 7
... with #!/bin/ksh and #!/bin/bash shebang

Getopts Basics

Syntax

getopts optstring name [args...]
optstring specifies all the option characters that the script will recognize
if a character is followed by :, then this option expects an argument
if optstring start with :, it switches getopt in "error silent mode"
name variable which stores the current option character
args [optional] specifies arguments to parse (default value is $@)

Special variables

Variable Description
OPTIND store the index of the next argument to be processed
Initially set to 1, and needs to be reset to 1 in order to parse anything else again
OPTARG stores the value of the option argument found by getopts

How it works

getopts is usually used in a while-loop and exit when it reaches the end of options.

while getopts ...; do
  ...
done
shift $((OPTIND-1))

getopts recognizes the end of the options by any of the following conditions:

  • an argument that doesn't start with -
  • the special argument --, marking the end of options
  • an error (for example, an unrecognized option letter)

Script example source code

foobar_any_getopts.sh is a script example to show how to use getopts with long options.

This script is also available for downloading at the end of the article.

#!/bin/ksh
#================================================================
#% SYNOPSIS
#+    ${SCRIPT_NAME} [-fb] [-B n] [-F[name]] files ...
#%
#% DESCRIPTION
#%    This is a script example
#%    to show how to use getopts with long options.
#%
#% OPTIONS
#%    -f, --foo                  set foo setting (default)
#%    -b, --bar                  set bar setting
#%    -B n, --barfoo=n           set barfoo to number n (default=2)
#%    -F [name], --foobar=[name] set foobar and foobar_name
#%    -h, --help                 print this help
#-
#- IMPLEMENTATION
#-    version         ${SCRIPT_NAME} (www.uxora.com) 0.0.2
#-    author          Michel VONGVILAY
#-    copyright       Copyright (c) http://www.uxora.com
#-    license         GNU General Public License
#================================================================

  #== general functions ==#
usage() { printf "Usage: "; head -50 ${0} | grep "^#+" | sed -e "s/^#+[ ]*//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; }
usagefull() { head -50 ${0} | grep -e "^#[%+-]" | sed -e "s/^#[%+-]//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; }

#============================
#  SET VARIABLES
#============================
unset SCRIPT_NAME SCRIPT_OPTS ARRAY_OPTS

  #== general variables ==#
SCRIPT_NAME="$(basename ${0})"
OptFull=$@
OptNum=$#

  #== program variables ==#
foo=1 bar=0
barfoo=2 foobar=0
foobar_name=

#============================
#  OPTIONS WITH GETOPTS
#============================

  #== set short options ==#
SCRIPT_OPTS=':fbF:B:-:h'
  #== set long options associated with short one ==#
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))

#============================
#  MAIN SCRIPT
#============================

  #== print variables ==#
echo foo=$foo bar=$bar
echo barfoo=$barfoo
echo foobar=$foobar foobar_name=$foobar_name
echo files=$@

Check how this script works

See how it manages options errors :

# Unknown option error
$ ./foobar_any_getopts.sh -x
<<--OUTPUT--
foobar_any_getopts.sh: -x: unknown option
Usage: foobar_any_getopts.sh [-fb] [-B n] [-F[name]] files ...
--OUTPUT--

# barfoo option without argument
# Expected argument error
$ ./foobar_any_getopts.sh -B
<<--OUTPUT--
foobar_any_getopts.sh: -B: option requires an argument
Usage: foobar_any_getopts.sh [-fb] [-B n] [-F[name]] files ...
--OUTPUT--

Now see it working without errors :

# foobar short option and barfoo short option
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
<<--OUTPUT--
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2
--OUTPUT--

# foobar short option and barfoo long option
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
<<--OUTPUT--
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2
--OUTPUT--

Better you test by yourself to see if it works for you.

Please leave comments and suggestions,
Michel.

Reference/Related articles
In recent ksh, getopts can parse long options
A nice and small getopts tutorial

Attachments:
Download this file (foobar_any_getopts.zip)foobar_any_getopts.zip1 kB

Enjoyed this article? Please like it or share it.

Add comment

Please connect with one of social login below (or fill up name and email)

     


Security code
Refresh