make getopts error when optionstring does not start with a dash - bash

I am writing a script that uses getopts for options that require arguments and some that do not. I want getopts to exit with an error if any switches do not start with a '-', rather than simply stop parsing the options and continue. So if this is my getopts line:
while getopts "a:e:f:o:q:r:djpsvV" opt; do
and I call the script like this:
script.sh -a word -o eat me -j -d -e testing
the script stops parsing at "me" and the remaining switches are ignored. I want it to error out when it reaches "me", because it does not start with a '-'. How do I do that?

After your while getopts loop, do this:
shift $((OPTIND-1))
if (( $# > 0 )); then
echo "error: extra args detected: $*" >&2
exit 1
fi

Related

Changing the value of the variable from another shell script

I have a shell script that has a on/off switch inside. I programmed the Housekeeping.sh to execute this certain line of code if the value is at 1 and don't execute it if it's at 0.
The following is the code on my Housekeeping.sh:
ARCHIVE_SWITCH=1
if [[ $ARCHIVE_SWITCH -eq 1 ]]; then
sqlplus ${BPS_SCHEMA}/${DB_PASSWORD}#${ORACLE_SID} #${BATCH_HOME}/sql/switch_archive.sql
fi
Now I want to create another shell script file that I'll execute to automatically change the variable ARCHIVE_SWITCHequals to 0 everytime I execute this script.
Is there any other way that I can change the value of the variable ARCHIVE_SWITCH from another shell script file that I'll execute manually?
I'd use an option to the script:
bash housekeeping.sh # default is off
bash housekeeping.sh -a # archive switch is on
#!/usr/bin/env bash
archive_switch=0
while getopts :a opt; do
case $opt
a) archive_switch=1 ;;
*) echo "unknown option -$opt" >&2 ;;
esac
done
shift $((OPTIND-1))
if ((archive_switch)); then
sqlplus ...
fi

Can a shell script flag have optional arguments if parsing with getopts?

I have a script that I want to run in three ways:
Without a flag -- ./script.sh
With a flag but no parameter -- ./script.sh -u
With a flag that takes a parameter -- ./script.sh -u username
Is there a way to do this?
After reading some guides (examples here and here) it doesn't seem like this is a possibility, especially if I want to use getopts.
Can I do this with getopts or will I need to parse my options another way? My goal is to continue using getopts if I can.
The non-getopts example in BashFAQ #35 can cover the use case:
user_set=0 # 1 if any -u is given
user= # set to specific string for -u, if provided
while :; do
case $1 in
-u=*) user_set=1; user=${1#*=} ;;
-u) user_set=1
if [ -n "$2" ]; then
user=$2
shift
fi ;;
--) shift; break ;;
*) break ;;
esac
shift
done

Getopts in sourced Bash function works interactively, but not in test script?

I have a Bash function library and one function is proving problematic for testing. prunner is a function that is meant to provide some of the functionality of GNU Parallel, and avoid the scoping issues of trying to use other Bash functions in Perl. It supports setting a command to run against the list of arguments with -c, and setting the number of background jobs to run concurrently with -t.
In testing it, I have ended up with the following scenario:
prunner -c "gzip -fk" *.out - works as expected in test.bash and interactively.
find . -maxdepth 1 -name "*.out" | prunner -c echo -t 6 - does not work, seemingly ignoring -c echo.
Testing was performed on Ubuntu 16.04 with Bash 4.3 and on Mac OS X with Bash 4.4.
What appears to be happening with the latter in test.bash is that getopts is refusing to process -c, and thus prunner will try to directly execute the argument without the prefix command it was given. The strange part is that I am able to observe it accepting the -t option, so getopts is at least partially working. Bash debugging with set -x has not been able to shed any light on why this is happening for me.
Here is the function in question, lightly modified to use echo instead of log and quit so that it can be used separately from the rest of my library:
prunner () {
local PQUEUE=()
while getopts ":c:t:" OPT ; do
case ${OPT} in
c) local PCMD="${OPTARG}" ;;
t) local THREADS="${OPTARG}" ;;
:) echo "ERROR: Option '-${OPTARG}' requires an argument." ;;
*) echo "ERROR: Option '-${OPTARG}' is not defined." ;;
esac
done
shift $(($OPTIND-1))
for ARG in "$#" ; do
PQUEUE+=("$ARG")
done
if [ ! -t 0 ] ; then
while read -r LINE ; do
PQUEUE+=("$LINE")
done
fi
local QCOUNT="${#PQUEUE[#]}"
local INDEX=0
echo "Starting parallel execution of $QCOUNT jobs with ${THREADS:-8} threads using command prefix '$PCMD'."
until [ ${#PQUEUE[#]} == 0 ] ; do
if [ "$(jobs -rp | wc -l)" -lt "${THREADS:-8}" ] ; then
echo "Starting command in parallel ($(($INDEX+1))/$QCOUNT): ${PCMD} ${PQUEUE[$INDEX]}"
eval "${PCMD} ${PQUEUE[$INDEX]}" || true &
unset PQUEUE[$INDEX]
((INDEX++)) || true
fi
done
wait
echo "Parallel execution finished for $QCOUNT jobs."
}
Can anyone please help me to determine why -c options are not working correctly for prunner when lines are piped to stdin?
My guess is that you are executing the two commands in the same shell. In that case, in the second invocation, OPTIND will have the value 3 (which is where it got to on the first invocation) and that is where getopts will start scanning.
If you use getopts to parse arguments to a function (as opposed to a script), declare local OPTIND=1 to avoid invocations from interfering with each other.
Perhaps you are already doing this, but make sure to pass the top-level shell parameters to your function. The function will receive the parameters via the call, for example:
xyz () {
echo "First arg: ${1}"
echo "Second arg: ${2}"
}
xyz "This is" "very simple"
In your example, you should always be calling the function with the standard args so that they can be processed in the method via getopts.
prunner "$#"
Note that pruner will not modify the standard args outside of the function.

Testing whether or not a flag has an argument

My typical setup for parsing command-line options is:
CONF=""
INPUT=""
while getopts ":c:i:" FLAG; do
case $FLAG in
i) INPUT=$OPTARG;;
c) CONF=$OPTARG;;
\?) echo -e "\nInvalid option: -$OPTARG"
usage;;
:) echo -e "Option -$OPTARG requires an argument."
usage;;
esac
done
if [ "$#" -eq 0 ]; then
usage
fi
I'm looking for a way to catch when a valid flag is provided but no argument is - for example:
./Script.sh -c -i
Returns usage. I was under the impression that this line:
:) echo -e "Option -$OPTARG requires an argument."
Handled this however when running the script as above using flags without arguments, the usage function is not firing nor is the echo.
What am I doing wrong?
If you invoke your script with either ./script.sh -c -i -c or ./script.sh -c, both will show the error message Option -c requires an argument.
However, when invoking ./script.sh -c -i, you are passing value "-i" for the -c argument, so that at the end of arguments parsing, you end up with CONF=-c and INPUT not set.

How make bash getopts not recognize option as a option argument

In my bash script, I would like to use getopts to parse command-line options.
My first attempt, to learn how to use it, is as follows:
#!/bin/bash
v_option_arg=""
r_option_arg=""
h_option_arg=""
function get_opt_args() {
while getopts ":v:r:h:" opt
do
case $opt in
"v")
v_option_arg="$OPTARG"
;;
"h")
h_option_arg="$OPTARG"
;;
"r")
r_option_arg="$OPTARG"
;;
"?")
echo "Unknown option -$OPTARG"
exit 1
;;
":")
echo "No argument value for option -$OPTARG"
;;
*)
# Should not occur
echo "Unknown error while processing options"
;;
esac
done
shift $((OPTIND-1))
}
get_opt_args $#
if [ ! -z "$v_option_arg" ]; then
echo "Argnument value for option -v: $v_option_arg"
fi
if [ ! -z "$r_option_arg" ]; then
echo "Argnument value for option -r: $r_option_arg"
fi
if [ ! -z "$h_option_arg" ]; then
echo "Argnument value for option -h: $h_option_arg"
fi
$ bash testopts.sh -v 1
Argnument value for option -v: 1
$ bash testopts.sh -r 2
Argnument value for option -r: 2
$ bash testopts.sh -h 3
Argnument value for option -h: 3
$ bash testopts.sh -v 1 -r 2 -h 3
Argnument value for option -v: 1
Argnument value for option -r: 2
Argnument value for option -h: 3
$ bash testopts.sh -v
No argument value for option -v
$ bash testopts.sh -a
Unknown option -a
This seems to work successfully.
Next, I test my script's robustness by omitting an argument:
$ bash testopts.sh -v -r 2
Argnument value for option -v: -r
This is not what I was expecting. How can I make it distinguish the differences of one option and one option argument?
I want to make my script more robust, so that if one option is given without its argument, I can emit a suitable error message.
Note: Each option must have a option argument.
Can I do this with just getopts?
From man bash, SHELL BUILTIN COMMANDS, getopts:
optstring contains the option characters to be recognized; if a character is followed by a colon, the option is expected to have an argument, which should be separated from it by white space.
From your script:
while getopts ":v:r:h:" opt
You told bash explicitly that -v expects an argument. In the case of -v -r 2, -r is the argument to -v, with 2 remaining as non-option argument to the script.
Works as designed, and this is the limit of getopts abilities.
What you can do is checking if the argument to -v is numeric (as it seems that is what your script expects), and in the given case inform the user that -v does require a number, and that -r isn't it. But that is something your script needs to do in the "v") case, not something getopts can handle.
case $opt in
"v")
v_option_arg="$OPTARG"
if [[ ! "${v_option_arg}" =~ ^[0-9]*$ ]]
then
echo "Error: Option '-v' expects numeric argument, you gave: ${v_option_arg}"
exit 1
fi
;;

Resources