Suppose I have a script a.sh to be invoked with options
a.sh -a1 a1option -a2 a2option
Suppose also I have a script b.sh, which invokes a.sh and uses its own options. So user executes the scripts as follows:
b.sh -b1 b1option -b2 b2option -a1 a1option -a2 a2option
Now I wonder how to parse the command line options in b.sh.
I do not need to parse the entire command line. I do not want b.sh to be aware of options a1 and a2. I would like to get only options b1 and b2 and pass the rest to a.sh.
How would you do it ?
As requested, this method avoids parsing the entire command line. Only the arguments up to -- are collected for b.sh. Then the arguments for b are stripped and only the remaining arguments are passed to a.sh.
b.sh is invoked with b.sh -b b1option -B b2option -- -a1 a1option -a2 a2option. In this line, the double dash -- indicates the end of options for b.sh. The following parses the options before the -- for use by b.sh, then removes the b arguments from the $# so you can pass it to a.sh without worrying about what errors a.sh might give you.
while getopts ":b:B:" opt; do
case $opt in
b) B1=${OPTARG}
;;
B) B2=${OPTARG}
;;
esac
done
## strips off the b options (which must be placed before the --)
shift $(({OPTIND}-1))
a.sh "$#"
A note: This method utilizes the bash builtin getopts. Getopts (as opposed to getopt, no s) takes only single-character options; hence, I have used b and B instead of b1 and b2.
My favorite getopts reference.
You can do something like this:
#!/bin/bash
while [[ $# -gt 0 ]]; do
case "$1" in
-b1)
B1=true
B1OPT=$2
shift
;;
-b2)
B2=true
B2OPT=$2
shift
;;
--)
shift
break
;;
*)
echo "Invalid option: $1"
exit 1 ## Could be optional.
;;
esac
shift
done
bash a2.sh "$#"
Note that you should place your variable $# inside doublequotes to prevent word splitting when expanded.
If a.sh can ignore options it doesn't know you can just call it with all the options b.sh was called:
a.sh "${#}"
Related
I am working on writing a bash shell script that has 4 command-line arguments. 3/4 arguments are one word and 1/4 is multi-word. I managed to get values for 2 of them, but can't get right values for 3rd and fourth. If I remove multi-work argumnent however, it works.
options=$(getopt -l "help,env:,site:,cluster:,cluster-group:" -o "he:s:c:cg:" -a -- "$#")
eval set -- "$options"
while true
do
case $1 in
-h|--help)
showHelp
exit 0
;;
-e|--env)
shift
export environment=$1
;;
-s|--site)
shift
export site=$1
;;
-c|--cluster)
shift
export cluster=$1
;;
-cg|--cluster-group)
shift
export cluster_group=$1
;;
--)
shift
break;;
esac
shift
done
echo $environment
echo $site
echo $cluster
echo $cluster_group
When ran sh b.sh -s S1 -e E1 -c C1 -cg CG1, output is
E1
S1
g
What am I doing wrong here?
As per man getopt(1): -o recognizes only one-character options.
-o, --options shortopts
The short (one-character) options to be recognized. If
this option is not found, the first parameter of getopt
that does not start with a '-' (and is not an option
argument) is used as the short options string. Each short
option character in shortopts may be followed by one colon
to indicate it has a required argument, and by two colons
to indicate it has an optional argument. The first
character of shortopts may be '+' or '-' to influence the
way options are parsed and output is generated (see
section SCANNING MODES for details).
So in your case ,you could mention only a single letter, something like :g after -o option for the cluster-group.
options=$(getopt -l "help:,env:,site:,cluster:,cluster-group:" -o "h:e:s:c:g:" -a -- "$#")
I am trying to execute my file by passing in an absolute path as the first argument ($1). I also want to add flags from that absolute path onward, but i do not know how to tell optargs to start counting from $2 forward since if i pass in the absolute path as the $1 it seems to break the getopts loop.
I'm gussing i have to implement a shift for the first argument in the following code:
while getopts :lq flag; do
case $flag in
l) echo "executing -l flag"
;;
q) echo "executing -q flag"
;;
esac
done
I'm not sure how to approach this. Any tips are welcome, thank you.
getopts does, indeed, stop processing the arguments when it sees the first non-option argument. For what you want, you can explicitly shift the first argument if it is not an option. Something like
if [[ $1 != -* ]]; then
path=$1
shift
fi
while getopts :lq flag; do
...
done
Keep the options before file argument (i.e. absolute path).
Many standard bash commands follow the same practice.
Example :
wc -wl ~/sample.txt
ls -lR ~/sample_dir
So if you follow the above practice, your code goes like this.
This code works even if options are not provided.
In general, that is the desired behavior with options.
# Consider last argument as file path
INPUT_FILEPATH=${*: -1}
echo $INPUT_FILEPATH
# Process options
while getopts :lq flag
do
case $flag in
l) echo "executing -l flag"
;;
q) echo "executing -q flag"
;;
esac
done
Sample execution :
bash sample.sh /home/username/try.txt
/home/username/try.txt
bash sample.sh -lq /home/username/try.txt
/home/username/try.txt
executing -l flag
executing -q flag
I'm learning the getopt command and using the following diagnostic script to study its workings:
$ cat test-getopt.sh
#!/bin/bash
args=`getopt ab:c $*`
set -- $args
for i
do
echo "-->$i"
done
echo $#
I cannot understand its behavour in the following cases. Could you clarify?
1st case:
$ ./test-getopt.sh -ab arg -c
-->-a
-->-b
-->arg
-->-c
-->--
5
Why does getopt add -- as $5? What does it mean here? To point out the end of options?
2nd case:
$ ./test-getopt.sh -ab arg c
-->-a
-- -b
-->arg
-->--
-->c
5
Now, getopt adds c as $5's value, after that --. It is not a option, what does c mean here?
Which kind of element is it -- option, or option's argument, or positional argument?
It's not defined in getopt's parameter specifying valid options, why doesn't the program raise an error?
I've already skimmed through the getopt man page as well as some tutorials but couldn't quite work out a clear explanation.
According to getopt manpage:
Normally, no non-option parameters output is generated until all
options and their arguments have been generated. Then '--' is
generated as a single parameter, and after it the non-option
parameters in the order they were found, each as a separate parameter.
I.e. -- by itself is generated to signify the end of options. (And after it, positional parameters are generated if there are any.)
I guess this is done for uniformity -- to use the same code logic regardless of whether the user specified -- on the command line or not.
In the 2nd case, c is a positional argument. Positional arguments are not checked by getopt in any way and are rather passed as-is. The manpage doesn't say anything about validating non-option arguments:
getopt is used to break up (parse) options in command lines for easy
parsing by shell procedures, and to check for legal options.
Finally, note that to correctly process arguments with whitespace, you need to: use $# instead of $*; quoting; eval with set; and use the enhanced mode of getopt -- as per Example of how to parse options with bash/getopt. Also should use bash -e mode to quit the program on an invalid option:
#!/bin/bash -e
args=`getopt -o ab:c -- "$#"`
eval set -- "$args"
for i
do
echo "-->$i"
done
echo $#
$ ./test-getopt.sh -b "arg ument"
-->-b
-->arg ument
-->--
3
$ ./test-getopt.sh -d ; echo $?
getopt: unknown option -- d
1
Also, a while loop with shift as per the same example could be more convenient that for as it: makes it easy to get the next argument -- to get the option's argument and check if there is an argument if it's optional; check the number of the remaining (positional) arguments when you're done with options.
I normally use constructs like this to run getopts:
# Set defaults
opt_a=0; opt_b=""; opt_c=false
# Step through options
while getopts ab:c opt; do
case "$opt" in
a) opt_a=1 ;;
b) opt_b="${OPTARG:?The -b option requires an argument.}" ;;
c) opt_c=true ;;
*) usage; exit 64 ;;
esac
done
shift $((OPTIND - 1))
Use of shift like this at the end causes your positional arguments to be shifted back such that the first argument that getopts can't process becomes $1. For example, if the above snippet was part of a script named foo, one might run:
$ foo -ab meh smoo blarg
which would set $opt_a to 1, $opt_b to "meh", $1 to "smoo" and $2 to "blarg" for the portion of the script following the snippet.
I'm creating a bash script which involves parsing arguments. The usage would be:
$ ./my_script.sh -a ARG_1 -b ARG_2 [-c LIST_OF_ARGS...]
Using getopts I'm able to parse -a and -b and get their respective values ARG_1 and ARG_2. If and only if user places -c as last argument, then I'm also able to get -c and create a list with all values in LIST_OF_ARGS....
But I would not like to force user to insert -c as the last flag. For instance, it would be great if the script can be invoked this way:
$ ./my_script.sh -b ARG_2 -c V1 V2 V3 -a ARG_1
Here is my current code:
while getopts a:b:c opt
do
case $opt in
a)
A_FLAG=$OPTARG
;;
b)
B_FLAG=$OPTARG
;;
c)
# Handle values as regular expressions
args=("$#")
C_LIST=()
for (( i=$OPTIND-1 ; i <= $#-1 ; i++ ))
do
C_LIST=("${C_LIST[#]}" ${args[$i]})
done
;;
?)
usage
;;
esac
done
You need to separate your detection of the -c flag with the processing associated with it. For example, something like:
while getopts a:b:c opt
do
case $opt in
a)
A_FLAG=$OPTARG
;;
b)
B_FLAG=$OPTARG
;;
c)
C_FLAG=1
;;
?)
usage
;;
esac
done
# discard all of our options.
shift `expr $OPTIND - 1`
if [ "$C_FLAG" = 1 ]; then
# Handle values as regular expressions
args=("$#")
C_LIST=()
for (( i=0 ; i <= $#-1 ; i++ ))
do
C_LIST=("${C_LIST[#]}" ${args[$i]})
done
fi
This script doesn't collect all the non-option arguments until after processing all the command line options.
Here's a question: why have a -c option at all?
If the full usage involves a list of values, why not just have no -c option and allow the -a and -b options only while the rest are regular args as in ./myscript.sh -a ARG_1 -b ARG_2 [argument ...], where any arguments are optional (like the -c option and its arguments are in your usage example?
Then your question becomes "how do I intersperse program options and arguments", to which I would respond: "You shouldn't do this, but to achieve this anyway, parse the command line yourself; getopts won't work the way you want it to otherwise."
Of course, parsing is the hard way. Another possibility involves adding the values after -c to a list, so long as you don't encounter another option or the end of the options:
C_LIST=()
while getopts a:b:c: opt; do
#Skipping code...
c)
C_LIST+="$OPTARG"
shift $(expr $OPTIND - 1)
while [ -n "$1" ] && [ $(printf "%s" "$1" | grep -- '^[^-]') ]; do
C_LIST+="$1"
shift
done
OPTIND=1
;;
The behaviour of getopts is mimicked: even if OPTARG begins with a '-' character, it is still kept, but after OPTARG, any string starting with the '-' character may simply be an invalid option such as -n. I used printf instead of echo because some versions of echo, such as the one that bash has built-in, have a -e option that may or may not allow the loop to continue, which isn't desired. The grep expression should prevent this, but who knows if that version of echo allows for -e'hello', which would cause grep to succeed because it sees "hello"? While possibly unnecessary, why take chances?
Personally, I'd avoid this behaviour if you can, but I also don't understand why you're asking for this behaviour in the first place. If I were to recommend anything, I'd suggest the more common /path/to/script -a ARG_1 -b ARG_2 [argument ...] style above any other possible choice of implementation.
On my system, I haven a /usr/share/doc/util-linux/examples/getopt-parse.bash file. It puts the result of getopt into a variable, and set the positional parameters to that variable. Then uses a switch similar to yours, but uses shift to remove arguments when found.
You could do something similar, but for your -c option use shift until you get an option or run out of arguments.
Or it might be enough for you to use your current solution, but remember to set the OPTIND variable after the loop.
I'm looking for a way to handle arguments containing blank spaces that has to be parsed
by shell getopts command.
while getopts ":a:i:o:e:v:u:" arg
do
echo "ARG is: $arg" >> /tmp/submit.log
case "$arg" in
a) arg1="$OPTARG" ;;
i) arg2="$OPTARG" ;;
o) arg3="$OPTARG" ;;
...
u) argn="$OPTARG" ;;
-) break ;;
\?) ;;
*) echo "unhandled option $arg" >> /tmp/submit.log ;;
?) echo $usage_string
exit 1 ;;
esac
done
Now if -u has argument like "STRING WITH WHITE SPACE"
than just the first part of the string is triggered and the while loop doesn't go to the end.
many thanks.
a trap for young players (ie me!)
beware a line like this:
main $#
what you really need is:
main "$#"
otherwise getopts will mince up your options into little pieces
http://www.unix.com/shell-programming-scripting/70630-getopts-list-argument.html
As Mat notes, your script fragment is already correct. If you're invoking your script from a shell, you need to quote arguments properly, e.g.
myscript -u "string with white space"
myscript -u 'string with white space'
myscript -u string\ with\ white\ space
myscript -u string' w'ith\ "whi"te" "''space
Requiring these quotes is not a defect in your script, it's the way the calling shell works. All programs, scripts or otherwise, receive arguments as a list of strings. The quotes in the calling shell are used to sort these arguments into separate “words” (list elements). All the calls above (made from a unix shell) pass a list of three strings to the script: $0 is the script name (myscript), $1 is -u and $2 is the string string with white space.