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).
Related
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.
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.
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.
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?
I'm trying to make script for multiple input data files. What is the best way, how to handle that arguments? Usage of script should be:
./script.sh -a sth -b sth - c sth -d sth input1 input2 input3
I can handle parameters and arguments using getopts, but I have no idea, how to handle these input files, because there is no flag for them.
Thanks
while getopts ":a:b:c:d:" opt; do
case "$opt" in
a) i=$OPTARG ;;
b) j=$OPTARG ;;
c) k=$OPTARG ;;
d) l=$OPTARG ;;
esac
done
shift $(( OPTIND - 1 ))
for file in "$#"; do
# your stuff here
done
Please try this, this may solve the purpose for you
My own comment has prompted me to extend the answer:
In case you want to do it only from getopts:
You will have to call the script as
./script -a hj -b op -c zx -d some -f "File in list seperated with spaces"
while getopts ":a:b:c:d:f:" opt; do
case "$opt" in
a) i=$OPTARG ;;
b) j=$OPTARG ;;
c) k=$OPTARG ;;
d) l=$OPTARG ;;
f) files=$OPTARG ;;
esac
done
#no shift is required now, since we have file list in $files
for file in $files; do
# your stuff here
done