Reputation: 8315
(Briskly, this question concerns mainly how to apply information to getopts
and how to apply a shift
to a manually-defined array.
I'm writing a procedure for the start of some Bash functions that parses parameters for a function in the usual getopts
way while also parsing unexpected parameters (by looping over getopts
processing multiple times).
In the code below can be seen a little function process_arguments()
which essentially contains the getopts
processing and a concluding shift
operation which acts on the positional parameters. Because this overall procedure for parameter parsing is destined for functions as opposed to scripts, I'd like to move the processing contained in the function process_arguments
to the main while
loop in the script. This involves a modification to the input and output information from the procedure in the function.
The input information for the getopts
processing can be passed to getopts
in the array args
:
getopts optstring name [args]
However, I'm not sure of how the output of the procedure can be changed. At present, it shifts the positional parameters and then it prints the positional parameters (echo "${@}"
).
So, questions...
getopts
args
approach the right way to pass information to the procedure?shift
to the parameters that are passed to getopts
and how can I then output these shifted parameters to the rest of the script's processing?Thanks for your help. I welcome any other guidance. The overall goal is to remove the function process_arguments()
and incorporate its functionality into the main ``while``` loop.
#!/bin/bash
################################################################################
#
# This is a test of command line parameter parsing that is explicitly non-POSIX.
# This approach handles
# - options (for getopts)
# - arguments of options (for getopts)
# - positional arguments (for getopts) and
# - non-option arguments (command line parameters not expected by getopts).
# All non-option arguments are placed in the string non_option_parameters. The
# silent error reporting mode of getopts is required. Following command line
# parameter parsing, input information can be accepted based on internal
# priority assumptions.
#
# example usage:
# ./script.sh
# non-option parameters:
# ./script.sh -a Dirac
# -a triggered with parameter Dirac
# non-option parameters:
# ./script.sh -a Dirac -b
# -a triggered with parameter Dirac
# -b triggered
# non-option parameters:
# ./script.sh -a Dirac Feynman -b
# -a triggered with parameter Dirac
# -b triggered
# non-option parameters: Feynman
# ./script.sh -a Dirac Feynman Born -b
# -a triggered with parameter Dirac
# -b triggered
# non-option parameters: Feynman Born
# ./script.sh -a Dirac Feynman Born Born -b
# -a triggered with parameter Dirac
# -b triggered
# non-option parameters: Feynman Born
# ./script.sh -a Dirac Feynman Born Born -b
# -a triggered with parameter Dirac
# -b triggered
# non-option parameters: Feynman Born Born
#
################################################################################
process_arguments(){
OPTIND=1
while getopts "${option_string}" option; do
case ${option} in
a)
echo "-a triggered with parameter "${OPTARG}"" >&2 # output to STDERR
;;
b)
echo "-b triggered" >&2 # output to STDERR
;;
\?)
echo "invalid option: -"${OPTARG}"" >&2 # output to STDERR
;;
esac
done
shift $(expr ${OPTIND} - 1)
echo "${@}"
}
option_string=":a:b"
non_option_parameters=""
parameters="${@}"
while [ ! -z "${parameters}" ]; do
parameters="$(process_arguments ${parameters})"
non_option_parameters="${non_option_parameters} "$(awk -F '[ \t]*-' '{print $1}' <<< "${parameters}")
parameters=$(awk -F '[ \t]*-' 'NF > 1{print substr($0, index($0, "-"))}' <<< "${parameters}")
done
echo "non-option parameters:${non_option_parameters}"
Upvotes: 2
Views: 1614
Reputation: 246827
I didn't spend a lot of time reading your code. I agree with you: do your options processing at the script's "main level" so you can work directly with the script's position parameters.
Here's a rewrite:
#!/bin/bash
while getopts ":a:b" option; do
case $option in
a)
echo "-a triggered with parameter $OPTARG" >&2
;;
b)
echo "-b triggered" >&2
;;
:)
echo "missing argument for option -${OPTARG}" >&2
;;
\?)
echo "invalid option: -${OPTARG}" >&2
;;
esac
done
shift $(( OPTIND - 1 ))
echo "remaining arguments:"
for (( i=1; i<=$#; i++ )); do
printf "%d: '%s'\n" $i "${!i}"
done
And in action:
$ bash ./opts.sh -a A -b B -c -d "foo bar" baz
-a triggered with parameter A
-b triggered
remaining arguments:
1: 'B'
2: '-c'
3: '-d'
4: 'foo bar'
5: 'baz'
You don't see any "invalid option" errors for "-c" and "-d" because the while getopts
loop ended when it hit the first non-option argument "B".
Update: calling getopts multiple times:
extra_args=()
while (( $# > 0 )); do
OPTIND=1
while getopts ":a:b" option; do
case $option in
a) echo "-a triggered with parameter $OPTARG" >&2 ;;
b) echo "-b triggered" >&2 # output to STDERR ;;
:) echo "missing argument for option -$OPTARG" >&2 ;;
?) echo "invalid option: -$OPTARG" >&2 ;;
esac
done
shift $(( OPTIND - 1 ))
if (( $# > 0 )); then
extra_args+=( "$1" )
shift
fi
done
echo "non-option arguments:"
printf "%s\n" "${extra_args[@]}"
$ bash ~/tmp/opts.sh -a A B -c -d "foo bar" baz -b
-a triggered with parameter A
invalid option: -c
invalid option: -d
-b triggered
non-option arguments:
B
foo bar
baz
Upvotes: 1
Reputation: 241741
The shiftless solution follows.
I use bash arrays to preserve positional (non-flag) parameters, because it's the only effective way to handle parameters with whitespace. In particular, your echo "${@}"
effectively wipes flattens arguments, despite the quoting, because echo
doesn't preserve quoting.
I also retain process_argument
, although you could easily put it in the loop. It makes very little difference (and directly calling exit 1
rather than return 1
is arguably simpler.)
# process_argument option
# Do whatever needs to be done to handle provided option. Fail on error.
OPTIONS=:a:b
process_argument() {
case "$1" in
a)
echo "-a triggered with parameter $OPTARG" >&2
;;
b)
echo "-b triggered" >&2 # output to STDERR
;;
:)
echo "missing argument for option -${OPTARG}" >&2
return 1
;;
\?)
echo "invalid option: -${OPTARG}" >&2
return 1
;;
esac
}
# Loop to handle all parameters
non_option_parameters=()
while true; do
while getopts "$OPTIONS" option; do
if ! process_argument "$option"; then exit 1; fi
done
if ((OPTIND > $#)); then break; fi
non_option_parameters+=(${!OPTIND})
((OPTIND++))
done
printf "Non-option parameters: "
printf "<%s> " "${non_option_parameters[@]}"; echo
Upvotes: 1