bash getopts not recognizing the second argument - bash

This is my entire script in its simplest form.
#!/bin/bash
src=""
targ=${PWD}
while getopts "s:t:" opt; do
case $opt in
s)
src=$OPTARG
;;
t)
targ=$OPTARG
;;
esac
shift $((OPTIND-1))
done
echo "Source: $src"
echo "Target: $targ"
I run this script as getopts_test -s a -t b
However, it always prints the pwd in front of the Target: and never b
What am I missing here?

The reason for why b is never printed is that the shift within the loop moves the processed options away after the first iteration, i.e. after a has been printed. Use of shift $((OPTIND-1)) is intended to access the possible given variadic parameters. Naturally, once you remove shift, targ gets reassigned to b, and ${PWD} is no longer included in it since you don't have concatenation of the strings (targ and the option of -t) anywhere.

An alternative to what #glenn-jackman suggested in his comment
would be this :
#!/bin/bash
src=""
targ=${PWD}
while getopts "s:t:" opt; do
case $opt in
s)
src=$OPTARG
echo "Source: $src"
;;
t)
targ=$OPTARG
echo "Target: $targ"
;;
esac
done
shift $((OPTIND-1)) # Turning to non-option arguments say a file name and so on.
Here you go with the natural flow of arguments without shifting.

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.

How do I port getopts logic from bash to cshell?

Background
In bash I have a working getopts interface as depicted below:
while getopts "a:b:c:d:" OPTION ; do
case "$OPTION" in
a) JET="$OPTARG" ;;
b) FUEL="$OPTARG" ;;
c) CAN="$OPTARG" ;;
d) MELT="$OPTARG" ;;
*) echo "Usage $0 -a A -b B -c C -d D"; exit 1 ;;
esac
done
shift $((OPTIND-1))
#Check out input parameters
for PARAM in JET FUEL CAN MELT; do
echo "$PARAM in [${!PARAM}]"
done
Question
What is the cshell translation for this? I cannot find a clear example of getopts (with an s) in cshell, yet there is an easily findable one for bash. This is distinct from attempting to use getopt, since getopts is an entirely different function from getopt.

Bash Shell - Reading option with multiple letters

I am working on a shell script that allows users to set options.
The script is working fine if the user specifies options seperately. For instance:
./my_file.sh -r -l directory1 directory2
The command above is working perfectly, however my script doesn't work if the user specifies the following command:
EDIT: What I mean is that the -l option is not recognized if the user enters the command below
./my_file.sh -rl directory1 directory2
This is the code I am using to read the options
while getopts 'lvibrd:v' flag; do
case "${flag}" in
l) option_l=true
shift ;;
v) option_v=true
shift ;;
i) option_i=true
shift ;;
b) option_b=true
shift ;;
r) option_r=true
shift ;;
d) option_d=true
shift ;;
*) echo "Invalid options ${flag}"
exit 1 ;;
esac
done
Is there a way to read options with multiple letters, such as -rl, using similar code?
Thank you in advance.
Try the following (simplified):
while getopts 'lr' flag; do
case "$flag" in
l) echo "option -l was set";;
r) echo "option -r was set";;
*) echo "Invalid options ${flag}"
exit 1 ;;
esac
done
shift $(($OPTIND - 1))
echo "$#"
Omitted shift makes the difference. The shifting confuses getopts builtin.
This works correctly for both single and combined options:
$ ./nic.sh -rl hello world
option -r was set
option -l was set
hello world
EDIT: I've added code to print the rest of arguments (after those processed by getopts).

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.

getopts printing help when no cmd. line argument was matched

I'm trying to use getopts in bash to parse command line arguments, but I couldn't figure out how to implement "default" action, if no argument was matched (or no cmdline argument given).
This is silghtly simplified version of what I've tried so far:
while getopts v:t:r:s:c: name;
do
case $name in
v) VALIDATE=1;;
t) TEST=1;;
r) REPORT=1;;
s) SYNC=1;;
c) CLEAR=1;;
*) print_help; exit 2;;
\?) print_help; exit 2;;
esac
done
Is there any (simple) way to make it call print_help; exit 2; on non matching input?
Looking between your question and the comments on Aditya's answer, I'd recommend the following:
[getopts]$ cat go
#!/bin/bash
function print_help { echo "Usage" >&2 ; }
while getopts vtrsc name; do
case $name in
v) VALIDATE=1;;
t) TEST=1;;
r) REPORT=1;;
s) SYNC=1;;
c) CLEAR=1;;
?) print_help; exit 2;;
esac
done
echo "OPTIND: $OPTIND"
echo ${##}
shift $((OPTIND - 1))
while (( "$#" )); do
if [[ $1 == -* ]] ; then
echo "All opts up front, please." >&2 ; print_help ; exit 2
fi
echo $1
shift
done
Since each of those are boolean flag options, you don't need (and in fact, do not want) the arguments, so we get rid of the colons. None of those characters are in IFS, so we don't need to wrap that in quotes, it will be one token for getopts anyway.
Next, we change the \? to a single ? and get rid of the *, as the * would match before the literal \?, and we might as well combine the rules into a single default match. This is a good thing, since any option specified with a - prefix should be an option, and users will expect the program to fail if they specify an option you don't expect.
getopts will parse up to the first thing that isn't an argument, and set OPTIND to that position's value. In this case, we'll shift OPTIND - 1 (since opts are 0-indexed) off the front. We'll then loop through those args by shifting them off, echoing them or failing if they start with a -.
And to test:
[getopts]$ ./go
OPTIND: 1
0
[getopts]$ ./go -t -v go go
OPTIND: 3
4
go
go
[getopts]$ ./go -d -v go go
./go: illegal option -- d
Usage
[getopts]$ ./go -t go -v go -d
OPTIND: 2
5
go
All opts up front, please.
Usage
Try the following workaround:
# Parse the arguments.
while getopts ':h?f:' opts; do
case ${opts} in
f) # Foo argument.
;;
# My other arguments.
\? | h | *) # Prints help.
grep " .)\ #" $0
exit 0
;;
esac
done
So basically -?/-h would print the parameters with comments based on its own source. Specifying : before options will print help for any other unknown argument also.
v:t:r:s:c: should be in double quotes
"v:t:r:s:c:"
Based on the script you posted, maybe you don't require all those colons :
Also you don't need *)
You need to provide a leading colon in the getopts option string if you want to enable ? to match an invalid option -- :vtrsc. Also you don't need the backslash before the ?

Resources