Shell script not reading getopts arguments on MacOS - bash

I have written a script that performs tasks according to switches used on command line. Here is my script:
Expects the git branch name and two optional switches
-r for resetting database
-t for merging training data
test.sh
merge_training=false
reset_database=false
while getopts ":tr" opt; do
case ${opt} in
t ) # process option t
echo "one"
$merge_training=true
;;
r ) # process option r
echo "two"
$reset_database=true
;;
esac
done
echo $reset_database
echo $merge_training
When I run this script with command:
sh test.sh branchname -r -t
It does not print one or two and the last statements prints:
false
false
What is wrong here?

What is wrong is your assignments. Your should have got 2 error messages saying:
false=true: command not found
Hint: Never put a $ on the left-side of an assignment:
The convention is to put options first, then extra parameters, so your command-line should be:
bash test.sh -r -t branchname
Use bash not sh. sh is a POSIX shell and is roughly a subset of bash (its complicated). Don't confuse the two.
merge_training=false
reset_database=false
while getopts ":tr" opt; do
case ${opt} in
t ) # process option t
echo "one"
merge_training=true # <<<<<<<<<<<<<<<
;;
r ) # process option r
echo "two"
reset_database=true # <<<<<<<<<<<<<<<
;;
esac
done
shift $(( OPTIND-1 ))
extra="$1"
echo $reset_database
echo $merge_training
echo $extra

when you remove the branchname as a param, then it works (- but you have to also remove the $ from $merge_training=true, else you try to false=true..)
So what you could do is to save the $1 param in a variable and simply shift.
Here the code:
#!/bin/bash
merge_training=false
reset_database=false
branchname="$1"
shift
while getopts ":tr" opt; do
case $opt in
t ) # process option t
echo "one"
merge_training=true
;;
r ) # process option r
echo "two"
reset_database=true
;;
esac
done
echo $branchname
echo $reset_database
echo $merge_training

Some of the points you could enhance on your script,
Variable assignments don't take $ on the left-hand-side
The command-line arguments are seeing an extra argument branchname before processing the actual OPTSTRING, you need to exclude the first parameter before getting into the getopts() call.
Always set the shell interpreter to the one you want to run your script with. In bash do #!/usr/bin/env bash
Always quote your shell variables unless you have a good reason not to.
The updated script should look like below if you are passing arguments as bash test.sh branchname -r -t
#!/usr/bin/env bash
merge_training=false
reset_database=false
branchname="$1"
shift 1
while getopts ":tr" opt; do
case "${opt}" in
t ) # process option t
echo "one"
merge_training=true
;;
r ) # process option r
echo "two"
reset_database=true
;;
esac
done
echo "$reset_database"
echo "$merge_training"

Related

Bash optarg fails to spot missing arguments

I am inexperienced with bash shell scripting, and have run into a problem with bash optarg
Here's a small script to reproduce the problem:
#!/bin/sh
while getopts ":a:b:" opt; do
case ${opt} in
a ) echo "a=$OPTARG"
;;
b ) echo "b=$OPTARG"
;;
\? ) echo "Invalid option: $OPTARG" 1>&2
;;
: ) echo "Invalid option: $OPTARG requires an argument" 1>&2
esac
done
When I try this:
./args.sh -a av -b bv
I get the expected result:
a=av
b=bv
But when I omit the argument for -a:
/args.sh -a -b bv
I get this unfortunate result:
a=-b
When I would expect an error to show that the value of -a is missing.
It seems to have taken the -b argument as the value for -a.
Have I done something wrong & how can I achieve the expected behaviour?
The only positive advice is how do you treat But when I omit the argument for '-a', you cannot just skip to the next subsequent option. By convention getopts a: means you are expecting to an provide an arg value for the flag defined.
So even for the omitting case, you need to define an empty string which means the value for the arg is not defined i.e.
-a '' -b bv
Or if you don't expect the -a to get any arg values, better change the option string to not receive any as :ab:.
Any other ways of working around by checking if the OPTARG for -a is does not contain - or other hacks are not advised as it does not comply with the getopts() work flow.
getopts doesn't support such detection. So there's no way to do that with getopts.
You can probably write a loop around the arguments instead. something like:
#!/bin/sh
check_option()
{
case $1 in
-*)
return 1
;;
esac
return 0
}
for opt in $#; do
case ${opt} in
-a) shift
if check_option $1; then
echo "arg for -a: $1"
shift
else
echo "Invalid option -a"
fi
;;
-b) shift
if check_option $1; then
echo "arg for -b: $1"
shift
else
echo "Invalid option -b"
fi
;;
esac
done

Bash: Extract remaining unflagged arguments (when using getopts)

I'm using the getopts command to process the flags in my bash script, using the structure found in this answer: https://stackoverflow.com/a/21128172/2230446
#!/bin/bash
t_flag='30'
print_usage() {
printf "Usage: ..."
}
while getopts 't:' flag; do
case "${flag}" in
t) t_flag="${OPTARG}" ;;
*) print_usage
exit 1 ;;
esac
done
echo "${t_flag}"
I run the script with the following command:
./test.sh -t dog cat fish
My goal is to extract cat fish to a variable so I can use it in the rest of the script, similar to how the t_flag was extracted to a variable.
Discard -t dog after parsing it, e.g:
shift $((OPTIND-1))
Then only cat and fish will be left as positional parameters. To extract them to a variable:
var=$*

bash getopts behavior different in while loop vs commandline

Commandline
$ getopts ":mnopq:rs" Option -q
$ echo $Option
?
$ echo $OPTARG
Given
$ cat getopt.sh
#!/bin/bash
echo '$#' is $#
while getopts ":mnopq:rs" Option
do
echo Option is $Option
echo OPTARG is $OPTARG
case $Option in
m ) echo "Scenario #1: option -m- [OPTIND=${OPTIND}]";;
n | o ) echo "Scenario #2: option -$Option- [OPTIND=${OPTIND}]";;
p ) echo "Scenario #3: option -p- [OPTIND=${OPTIND}]";;
q ) echo "Scenario #4: option -q-\
with argument \"$OPTARG\" [OPTIND=${OPTIND}]";;
# Note that option 'q' must have an associated argument,
#+ otherwise it falls through to the default.
r | s ) echo "Scenario #5: option -$Option-";;
* ) echo "Unimplemented option chosen.";; # Default.
esac
done
I get
Script
$ ./getopt.sh -q
$# is -q
Option is :
OPTARG is q
Unimplemented option chosen.
Why is there a difference in output between Commandline and Script ?
Have you run the "Commandline" test multiple times in the same shell? getopts uses the variable OPTIND to keep track of where it is in the argument list, so it doesn't just process the same option over and over. As a result, if you run the test multiple times it'll skip over what it processed last time. In your case, I suspect this is making it think it's at the end of the argument list (in which case it'll have an exit status of 1). Here's an excerpt from the bash man page:
[...] Each time it is invoked, getopts places [...] the index
of the next argument to be processed into the variable OPTIND. OPTIND
is initialized to 1 each time the shell or a shell script is invoked.
[...] The shell does not reset OPTIND automatically; it
must be manually reset between multiple calls to getopts within the
same shell invocation if a new set of parameters is to be used.
Here's an example:
$ getopts ":mnopq:rs" Option -q
$ echo "status=$?, Option='$Option', OPTARG='$OPTARG', OPTIND=$OPTIND"
status=0, Option=':', OPTARG='q', OPTIND=2
$
$ getopts ":mnopq:rs" Option -q
$ echo "status=$?, Option='$Option', OPTARG='$OPTARG', OPTIND=$OPTIND"
status=1, Option='?', OPTARG='', OPTIND=2
$
$ unset OPTIND
$ getopts ":mnopq:rs" Option -q
$ echo "status=$?, Option='$Option', OPTARG='$OPTARG', OPTIND=$OPTIND"
status=0, Option=':', OPTARG='q', OPTIND=2
The first time it does what you expect. The second time it indicates it's out of options to process, which matches what you're seeing. The third time OPTIND has been unset, so it defaults back to the beginning of the argument list.

Bash script - how to read file line by line while using getopts

I want to do two things in this script:
1) pass a file name to the script
2) pass options to the script
example 1:
$./test_script.sh file_name_to_be_read
pass only file names to script
example 2:
$./test_script.sh -a -b file_name_to_be_read
pass file name and options to script
I am able to get example 1 to work using the following codes:
while read -r line ; do
echo $line
done
In example 2, I want to add additional flags like these:
while getopts "abc opt; do
case "$opt" in
a) a=1
echo "a is enabled"
;;
b) b=1
echo "b is enabled"
;;
esac
done
but how do I make it so that the file_name to be mandatory and be used with or without options?
getopts only parses options (arguments starting with -); the other arguments are left alone. The parameter OPTIND tells you the index of the first argument not yet looked at; typically you discard the options before this.
while getopts "ab" opt; do
case "$opt" in
a) a=1
echo "a is enabled"
;;
b) b=1
echo "b is enabled"
;;
esac
done
shift $((OPTIND - 1))
echo "$# arguments remaining"
for arg in "$#"; do
echo "$arg"
done
The preceding, if called as bash tmp.bash -a -b c d e, produces
$ bash tmp.bash -a -b c d e
a is enabled
b is enabled
3 arguments remaining:
c
d
e

bash: getopts: parse two options with arguments

I am trying to parse two options which both need an argument.
#!/bin/bash
while getopts "a:b:" opt; do
case $opt in
a)
echo "a has the argument $OPTARG."
shift
;;
b)
echo "b has the argument $OPTARG."
shift
;;
esac
done
What I expect is that this script prints out the arguments of a and b. But the output is only:
$ sh ./cmd_parse_test.sh -a foo -b bar
a has the argument foo.
What am I doing wrong?
You don't have to shift to get the next argument. Simply dump whatever you want, and continue for the next iteration, as in:
#!/bin/bash
while getopts "a:b:" opt; do
case $opt in
a)
echo "a has the argument $OPTARG."
;;
b)
echo "b has the argument $OPTARG."
;;
esac
done
Which outputs:
$ ./cmd_parse_test.sh -a foo -b bar
a has the argument foo.
b has the argument bar.
Notice also that you don't have to run the script with sh, since you've already set the shbang to use bash.

Resources