bash: getopts: parse two options with arguments - bash

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.

Related

Extending getopts from sourced script

I am trying to extend the getopts from the sourced script as below.
if an option other than a,b,c,d is passed in, it needs to print "invalid option" and exit.
script_a.sh:
#!/bin/bash
script_a_getopts() {
while getopts 'a:b:' OPT; do
case "$OPT" in
a)
a=$OPTARG
;;
b)
b=$OPTARG
;;
*)
echo "invalid option"
exit 1
;;
esac
done
}
script_b.sh
#!/bin/bash
source script_a.sh
while getopts ':c:d:' OPT; do
case "$OPT" in
c)
c=$OPTARG
;;
d)
d=$OPTARG
;;
[?])
script_a_getopts $#
esac
done
echo "a=$a"
echo "b=$b"
echo "c=$c"
echo "d=$d"
When I run the script, it don't work as expected, obviously I am making a mistake.
$ ./script_b.sh -c cat -d dog -a apple -b boy
a=
b=
c=cat
d=dog
Didn't throw error when -x is passed.
$ ./script_b.sh -x
a=
b=
c=
d=
Short Answer: You have to rollback the OPTIND before calling script_a_getopts.
case "$OPT" in
c)
c=$OPTARG
;;
...
[?])
let OPTIND--
script_a_getopts $#
esac
done
Long Answer:
The getopts track which arguments have been processed using the OPTIND variable. When the top getopts recognized unknown item, it has already 'consumed' it by moving OPTIND to the next argument. To get script_a_getopts to process that argument, the OPTIND need to be rolled back to point to the unprocessed argument.
The let OPTIND-- will allow the unrecognized argument to be re-processed.
Side note, if you want to allow options to be placed in arbitrary order (-c cat -a apple -d dog -b boy) you will have to repeat the same in the script_a_getopts. This will require improved error processing in script_a_getopts function.

Shell script not reading getopts arguments on MacOS

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"

Getopts considers my option chain as the argument for the first one

The following code is executed in subscript.sh, and subscript.sh is run inside primaryscript.sh
#### primaryscript.sh
#! /bin/bash
#...
"$bss_path"/subscript.sh "$option"
#...
I am using this code to parse my arguments and their values:
#### subscript.sh
#! /bin/bash
while getopts "hw:l:s:b:" opt; do
case $opt in
w)
x="$OPTARG";;
l)
xx="$OPTARG";;
s)
xxx="$OPTARG";;
b)
xxxx="$OPTARG";;
h)
print_usage
exit 0;;
\?)
echo "Invalid option: -$OPTARG"
exit 1;;
esac
done
The problem appears when I call my script with multiple arguments:
./myscript -l 5.0.3-0 -s 4.0-0 -b 010
getopts thinks that option l have 5.0.3-0 -s 4.0-0 -b 010 as argument.
What am I doing wrong?
I tried to play around with the : and the option, but as I understood I have to put them behind the options taking an argument right?
And getopts naturally knows that - is the separator for arguments.
$> echo $BASH_VERSION
$> 3.2.25(1)-release
As Cyrus pointed out in the comment, the problem was how I passed arguments.
./myscript "$options"
The correct way is :
./myscript $options
Since "$options" won't care of the spaces and take the whole string as a single argument.
It works for me. You can read more about GETOPTS, OPTARG as well as the while loop and case statement by typing "man bash" in your shell and searching.
#!/bin/bash
while getopts "hw:l:s:b:" opt; do
case $opt in
w)
x="$OPTARG";;
l)
xx="$OPTARG";;
s)
xxx="$OPTARG";;
b)
xxxx="$OPTARG";;
h)
print_usage
exit 0;;
\?)
echo "Invalid option: -$OPTARG"
exit 1;;
esac
done
echo $xx
echo $xxxx
echo $xxx
Output:
5.0.3-0
010
4.0-0

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

Pass parameters as option in custom getopts script in bash

I'd like to pass options as a parameter. E.g.:
mycommand -a 1 -t '-q -w 111'
The script cannot recognize a string in quotes. I.e it gets only part of the string.
getopts works the same - it see only -q.
For custom getopts I use similar script (example):
while :
do
case $1 in
-h | --help | -\?)
# Show some help
;;
-p | --project)
PROJECT="$2"
shift 2
;;
-*)
printf >&2 'WARN: Unknown option (ignored): %s\n' "$1"
shift
;;
*) # no more options. Stop while loop
break
;;
--) # End of all options
echo "End of all options"
shift
break
;;
esac
done
Maybe I misunderstand the question, but getopts seems to work for me:
while getopts a:t: arg
do
case $arg in
a) echo "option a, argument <$OPTARG>"
;;
t) echo "option t, argument <$OPTARG>"
;;
esac
done
Run:
bash gash.sh -a 1 -t '-q -w 111'
option a, argument <1>
option t, argument <-q -w 111>
Isn't that what you want? Maybe you missed the : after the options with arguments?

Resources