[BASH] Using getopts

Heyas

Just recently there was a thread about parsing arguments, where i read the first time about getopts.
This said, i'd like to 'provide' a list function that can be 'trigered' using an 'option'(?).

The regarding code snippets are:

	while getopts "e:(edit)l:(list)m:(mount)u:(unmount)h:(help)d:(debug)": name
	do 	case $name in
		e|edit)	server_info="$OPTARG";shift
			mode=edit ;;
		l|list)	mode=list ;;
		m|mount)server_info="$OPTARG";shift
			mode=mount ;;
		h|help)	printf "$help_text"
			exit $RET_HELP	;;
		u|unmount)
			server_info="$OPTARG";shift
			mode=unmount ;;
		d|debug)	DEBUG=true	;;
		esac
	done
	$DEBUG && set -x
	shift

... more code ...

	List() { # 
	# Prints a list of NAS' and their shares
	#
		ListNAS
	}

... more code ...

#
#	Display & Action
#
	case $mode in
	edit)	Edit
		exit $? ;;
	list)	List
		exit $? ;;
	menu)	menu="Mount Unmount Edit List New"
		tui-echo "Please choose a task:"
		select TASK in $menu;do
			$TASK
			exit $?
		done
		;;
	esac

The behaviour that irritates me is this:
on passing script -l it fails, on passing the long optionname -list it works as expected. (see attached screenshot)
However... -d|-debug is NOT affected by this.. why?

Where lies my problem, if there is one?
Is the getopts/shift thingy properly adapted in general?

Thank you in advance

Don't mix up the getopt command with bash's builtin getopts . The former allows for long options which the latter doesn't. And, colons in the OPTSTRING denote options with required arguments (which is indicated in your screenshot). So, in your case, try again with the OPTSTRING elmuhd - no long option, no req. arguments. I guess, you should forget the shift in the case statement as every call to getopts will get the next option.

1 Like

Thank you Rudi, i'm not fully understand yet.

When i change from getopts to getopt, and run "nas3.sh -l" all i get is "-- name" printed X-times. So i change back to the builtins getopts.
getopts: Man Page for getopts (linux Section 1) - The UNIX and Linux Forums
getopt: Man Page for getopt (linux Section 1) - The UNIX and Linux Forums
A slight diffrence in name, a big one in usage....

Pardon me, what does my screenshot indicate??

There are no colons (':') in OPTSTRING (actualy the 'value to the '-option' right?) or ANY argument expected.

Issue solved, so thank you again :slight_smile:
But i still dont feel 'comfortable', as i'd like to understand, why -d (ebug) worked without arguments, but -h (elp) and -l (ist) required some or had to be long?

Your screenshot says: option requires an argument -- l when run with -l only. When supplying -list , l is the option, and ist is the argument.

This is your OPTSTRING: "e:(edit)l:(list)m:(mount)u:(unmount)h:(help)d:(debug)": It has many an option character, some listed twice or even more often, and e, l, m, u, h, and d have a colon next to them, making them require an argument. -d works without arguments as it is listed in "edit" without colon. i should work as well as an option...

1 Like

Wierd, the article (some website i dont recall) showed some examples for multiple long-options in the like of:

e:(env)(envir)(environment)

Also it differs quite much from the example in: /usr/share/doc/util-linux/getopt-parse.bash which i found just now.
However i dont understand how getopt actualy gets involved: (snipet from the above file)

TEMP=`getopt -o ab:c:: --long a-long,b-long:,c-long:: \
     -n 'example.bash' -- "$@"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

while true ; do
	case "$1" in

However, i now understand the 'matters' of the colons :wink:

This might help you with long arguments using getopt:

eval set -- $(getopt -l edit:,list:,mount:,unmount:,help,debug -o e:l:m:u:hd -- "$@")

while true
do
   name="$1"
   shift
   case $name in
       -e|--edit) server_info="$1"; shift ; mode=edit ;;
       -l|--list) server_info="$1"; shift ; mode=list ;;
       -u|--unmount) server_info="$1"; shift ; mode=unmount ;;
       -d|--debug) DEBUG=true ;;
       -h|--help) printf "$help_text" ; exit $RET_HELP ;;
       --) break ;;
       *)  echo "Internal error!" ; exit 1 ;;
   esac
done
echo options left $#
echo Debug: $DEBUG Mode: $mode  Server_info: $server_info
1 Like

List should have an optional (IF any at all) argument, saying, if one provides the servername or ip, it lists just that one, otherwise (empty,missing) it lists all.
So assume to 'remove' the argument requirement, but check if anything else were supplied..

Changes:

eval set -- $(getopt -l edit:,list,mount:,unmount:,help,debug -o e:l:m:u:hd -- "$@")

And then check within the 'list' function like [ -z $server_info ] && echo 'list all' || echo 'list only $server_info'

And thanks to the local example and Chubler's adaption of my code, i finaly understood that shift thing.

Thank you guys, you're the best! :slight_smile:

The option string argument you were passing to getopts would be acceptable to recent versions of the Korn shell getopts built-in. (I'm not sure when this was added to ksh , but it was not in the early versions of ksh93. I am not aware of any version of bash that accepts this form of option specification for its getopts built-in.) Note that having a : after the short option name specifies that that option takes an option argument. (Also note that the case statement processing your command line arguments does not expect option arguments for the -d , -h , and -l options; so there should not have been a colon following these letters in the first operand to getopts . And, with ksh , long option names must be presented using a double leading minus sign (e.g., --list ) while short options are presented using a single leading minus sign (e.g., -l ) and multiple short options (without option-arguments) can be grouped behind a single minus sign (e.g., -dl ).

In general optional option-arguments are a bad idea. However, you can assign an empty string as the value of an option argument and behave appropriately if the assigned option-argument value is the empty string.

If you have a recent version of ksh , play around with script to see getopts works with short and long options:

#!/bin/ksh
# Initialize variables:
DEBUG=false
IAm=${0##*/}
RET_HELP=2
help_text="Usage:	$IAm -h
	$IAm [-dl] [-e server] [-m server] [-u server]
	$IAm --help
	$IAm [--debug] [--list] [--edit=server] [--mount=server] \\
		[--unmount=server]"
mode=""
server_info="No server set"

# Process options:
while getopts "d(debug)e:(edit)h(help)l(list)m:(mount)u:(unmount)": name
do 	case $name in
	(e|edit)server_info="$OPTARG"
		mode=edit;;
	(l|list)mode=list;;
	(m|mount)
		server_info="$OPTARG"
		mode=mount;;
	(h|help)printf "%s\n" "$help_text"
		exit $RET_HELP;;
	(u|unmount)
		server_info="$OPTARG"
		mode=unmount;;
	(d|debug)DEBUG=true;;
	(?)	printf "%s\n" "$help_text"
		exit $BAD_OPTION;;
	esac
done
$DEBUG && set -x
shift OPTIND-1

printf "DEBUG=%s, mode=%s, server_info=%s\n" "$DEBUG" "$mode" "$server_info"
printf "Number of remaining operands: %d\n" $#
while [ $# -gt 0 ]
do	printf "\toperand:%s\n" "$1"
	shift
done

If you save this code in a file named tester , make it executable:

chmod +x tester

Then you can see that all of the commands:

./tester -e eserver operand1
./tester -eeserver operand1
./tester --edit eserver -- operand1
./tester --edit=eserver operand
./tester -e eserver -- operand1
./tester --edit eserver operand1
./tester --edit=eserver -- operand

all produce exactly the same output:

DEBUG=false, mode=edit, server_info=eserver
Number of remaining operands: 1
	operand:operand1

while the command:

./tester --list

works as expected, but

./tester -list

will not work because with a single minus sign, only short options are allowed and there is no -i option defined, and produces the diagnostic:

./tester: -i: unknown option
Usage:	tester -h
	tester [-dl] [-e server] [-m server] [-u server]
	tester --help
	tester [--debug] [--list] [--edit=server] [--mount=server] \
		[--unmount=server]

To see how to specify an empty string an an option argument, try:

./tester -m ""
./tester --mount ""
./tester --mount=
./tester --mount=""

but note that the following will not work:

./tester -m""
./tester --mount""
1 Like