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.
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.
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 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).
I'm writing a bash script, that can take three options: c, p, and m; c can't have arguments, but p and m must have arguments. In my script I have these lines of code:
while getopts ":cp:m:" opt
do
case $opt in
c ) capitals=1
;;
m ) length=$OPTARG
;;
p ) number=$OPTARG
;;
\? ) echo "ERROR" 1>&2
exit 1
;;
esac
done
shift `expr $OPTIND - 1`
Now, when I write in the command line
myScript.sh -c -p 15 -m 12
all goes perfect. But when I write
myScript.sh -cp15m12
it goes wrong. Bash interprets the argument of the option 'p' as "15m12", while it should just be "15".
How can I solve this?
Since command-line arguments are not typed, there is no way to tell bash that the argument to -p must be an integer, and therefore no way for bash to infer that the correct way to parse -cp15m12 is as -c -p15 -m12 rather than as -c -p15m12. Since -c does not take an argument and getopts cannot define multi-character options, bash can infer that -cp15 is split into two separate options, -c and -p.
The value of getopts is fairly limited -- it's not hard to write your own replacement which supports option clustering. Here's a quick ad-hoc attempt.
#!/bin/sh
while true; do
case $1 in
-*) opt=${1#-}; shift;;
*) break;;
esac
while [ "$opt" ]; do
case $opt in
'-') opt=""; break 2;;
c*) capitals=1; opt=${opt#c};;
m[0-9]*)
opt=${opt#m}; length=${opt%%[!0-9]*}; opt=${opt#$length};;
m) opt=""; length=$1; shift;;
p[0-9]*)
opt=${opt#p}; number=${opt%%[!0-9]*}; opt=${opt#$number};;
p) opt=""; number=$1; shift;;
h|\?) echo "Syntax: $0 [-c] [-m length] [-p number]" >&2; exit 1;;
*) echo "Invalid option '$opt'" >&2; exit 2;;
esac
done
done
cat <<HERE
capitals: '$capitals'
length: '$length'
number: '$number'
args: '$#'
HERE
(Not sure if break 2 is entirely compatible with stock sh. Works for me on dash.)
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