getopts in bash programming - bash

when i am using 'while getopts d:n: OPTION' - how do i know that i got only invalid option and if yes that there is an argument after it?

You know you have an invalid option because the return value is '?'.
Either:
You can't tell whether it had an argument because the option was invalid.
Or:
You have to look at the next argument and see whether it starts with a dash.
The heuristic in the 'Or' option is imperfect. With option bundling, I could write:
command -xbf/fidget
If the getopts option string is 'xf:', then the 'b' is the invalid option.
If the getopts option string is 'xbf:', then there would be an option 'f' and the option argument '/fidget' after the valid option 'b'.
If the getopts option string is 'xb:f:', then the option argument for 'b' would be 'f/fidget'.
I think the 'either' attitude is correct - you cannot tell.
Code fragment from a command called 'rcsunco' - to cancel RCS checkouts (and written at a time when I was reluctantly moving off SCCS):
remove=yes
keep=no
get=no
quiet=
while getopts gknqrV opt
do
case $opt in
V) echo "`basename $0 .sh`: RCSUNCO Version $Revision: 2.1 $ ($Date: 2002/08/03 07:41:00 $)" |
rcsmunger
exit 0;;
g) get=yes;;
k) keep=yes;;
n) remove=no;;
q) quiet=-q;;
r) remove=yes;;
*) echo "Usage: `basename $0 .sh` [-{n|g}][-{r|k}] file [...]" 1>&2
exit 1;;
esac
done
shift $(($OPTIND-1))
These days, I'd used the 'balanced parentheses' notation in the 'case':
(q) quiet=-q;;
Also note that I do not explicitly test which option is returned - I let the catchall '*' case deal. I also observe that the usage message is not complete (no '-V' or '-q' documented), and the code is old enough that I haven't added a '-h' for help option. The script 'rcsmunger' replaces '$Revision 2.1 $' with just '2.1', more like the way SCCS replaces '%I%' with '2.1'.

An invalid option will have a question mark ?... IIRC processing of the command line arguments processing stop if there's an question mark...

Related

adding arguments to wrapper shell script

I have a wrapper shell script that needs to add some library and include paths before calling a compiler.
#!/bin/sh
LDIRS="-L/opt/lib"
LDFGS="-llibA -llibB"
exec /opt/bin/xxx $# $LDIRS $LDFGS
this works fine for compiling a simple test case
compiler -o test test.c
It falls apart if another program would like to call my compiler and pass in include directories like this
compiler -o in_file.xx -I/xxx -I/xxx
How could I generalize this to get the expected behavior of appending those includes to LDFGS?
I take it that the order of the LDIRS and LDFGS is the relevant issue; the additional things the user provides are supposed to end up in the LDFGS (i. e. need to be given after all the LDIRS to /opt/bin/xxx) but don't with your current implementation. So I take it that the -I/xxx arguments are to be added to the LDFGS. If I misunderstood your issue, please state more clearly what you wanted. Otherwise read on.
You have two options to solve an issue like this. Either you implement some kind of intelligence which understands the given options and sorts them properly into the lists where they belong. An approach to this could be like this:
#!/bin/sh
LDIRS="-L/opt/lib"
LDFGS="-llibA -llibB"
PRE=""
for argument in "$#"
do
case "$argument" in
-I*)
LDFGS="$LDFGS $argument"
;;
# other patterns could go here as well
*) # else case
PRE="$PRE $argument"
;;
esac
done
exec /opt/bin/xxx $PRE $LDIRS $LDFGS
As #charles-duffy already pointed out, using a bash or similar which supports proper lists would be way more robust in case any of your arguments contain spaces. If you are sure this won't happen, you should be fine for now. But your code maintainer will hate you for such things when they run into trouble because of this. So here's a less readable version in sh which should take care of this:
#!/bin/sh
LDIRS="-L/opt/lib"
LDFGS="-llibA -llibB"
PRE=""
for argument in "$#"
do
case "$argument" in
-I*)
LDFGS=`printf "%s %q" "$LDFGS" "$argument"`
;;
# other patterns could go here as well
*) # else case
PRE=`printf "%s %q" "$PRE" "$argument"`
;;
esac
done
eval "exec /opt/bin/xxx $PRE $LDIRS $LDFGS"
(Whenever eval is used, a disclaimer needs to be added: eval has it's dangers, some of them security-relevant, so please learn more about it before applying it wildly. The current unmodified case should be fine, though.)
If you think that you have no way of knowing all patterns which are supposed to go into LDFGS, LDIRS, and PRE, you need to hand this over to the user. Unfortunately, then the user needs to know more and pass this information. The calls then will need to look differently.
One way would be this:
#!/bin/sh
LDIRS="-L/opt/lib"
LDFGS="-llibA -llibB"
PRE=""
while [ $# -gt 1 ]
do
case "$1" in
-p) # go into PRE
PRE="$PRE $2"
shift 2
;;
-i) # go into LDIRS
LDIRS="$LDIRS $2"
shift 2
;;
-f) # go into LDFGS
LDFGS="$LDFGS $2"
shift 2
;;
*)
echo "Not understood: $1"
exit 1
;;
esac
done
eval "exec /opt/bin/xxx $PRE $LDIRS $LDFGS"
Now the call needs to look like this:
compiler -p "-o in_file.xx" -f "-I/xxx -I/xxx"
And again, if you have spaces issues, you should consider using proper array solutions or at least the ugly workaround I proposed above.

Cumulating bash parameters with an environment variable and parsing them altogether

I have a script that receive parameters from user input, but can have also have parameters coming from an environment variable.
e.g. :
export ADZ_DEPLOY_OPT="--from-git --rootDir XXXXXXX"
and in my script, I attempt to do something like :
$*="${ADZ_DEPLOY_OPT} $*"
while [[ $# -gt 1 ]]
do
key="$1"
case $key in
--rootDir)
ADZ_ROOT_DIR="$2"
shift # past argument
;;
--tagVersion)
ADZ_TAG_VERSION="$2"
shift # past argument
;;
--from-git)
ADZ_DEPLOY_FROM_GIT=1
;;
*)
# unknown option
;;
esac
shift # past argument or value
done
Except it doesn't work as it generates an error when I try to update $* to expand it with additional values - and I couldn't succeed to find a way to achieve this behavior.
The idea also is that the env variable will act as 'default' but may be overriden by what the user explicitly provide on the command line. That is with my example, if the user specify another rootDir, I want the user rootDir to be taken into account.
At last, I would like to keep long parameter names (I like being explicit) and therefore the use of getopts doesn't seem like an option.
Any help would be much appreciated !
Thanks in advance.
Replace
$*="${ADZ_DEPLOY_OPT} $*"
with:
set -- ${ADZ_DEPLOY_OPT} "$#"
Notes:
As bash is designed, one cannot assign directly to $*. Assignments are done with the set builtin.
You want to use "$#" not $*. The form $* is subjected to word splitting and the expansion of "$#" is not.
From your sample value for ADZ_DEPLOY_OPT, you do need word splitting for it. If you try to put complicated arguments in ADZ_DEPLOY_OPT, this won't work: you will need to use an array instead.

Bash case statement: retrieve list of valid options?

Is there any way to retrieve a list of the options in a case statement? For example if I have this code:
tool=$1
case ${tool} in
brdf)
# Do stuff
;;
drift)
# Do other stuff
;;
*)
echo "ERROR: I don't know this tool. Valid options are: brdf, drift"
exit 1
;;
esac
This is easy to read, but the error message could easily get out of date when adding/removing tools from the list as I need to remember to change the names there too.
The repetition could be avoided using an array something like this:
tool=$1
validtools=(brdf drift)
case ${tool} in
${validtools[0]})
# Do stuff
;;
${validtools[1]})
# Do other stuff
;;
*)
echo "ERROR: I don't know this tool. Valid options are: ${validtools[#]}"
exit 1
;;
esac
But that is pretty horrible to read, and in any case would be even worse to maintain with the hardcoded array indices.
Is there a good way of doing this, perhaps some variable or command that retrieves a list of the available options, or do I just have to remember to update the error message when I add a new option?
The most used way is as your 1st example. See all init scripts in linux.
And it is for reason, because you can use constructions like:
case "$a" in
arg1|arg2|arg3) ... ;;
brg1|brg2) ... ;;
brg2) ... ;;
esac
and would be hard contstuct automatically the right usage message with the all possible variants.
And here is the shopt -s extglob too, what allows you to use extended pattern matching in the case statemens. For examples see this answer: https://stackoverflow.com/a/4555979/632407
But if you want use arrays, try to use associative array, what add a bit of readability. (But it is terrible anyway) :) Like the next:
declare -A arg
initargs() { for a in "$#"; do arg[$a]="$a"; done; }
initargs brd lbrs ubrs
for myarg
do
case "$myarg" in
${arg[brd]}) echo "brd";;
${arg[ubrs]}) echo "ubrs";;
${arg[lbrs]}) echo "lbrs";;
*) echo "Unknown arg =$myarg=. Known are: ${arg[#]}" ;;
esac
done
So the allowed args are: "brd" "lbrs" "ubrs" and the script for the next input
$ bash argtest ubrs badarg brd
produces:
ubrs
Unknown arg =badarg=. Known are: lbrs ubrs brd
brd

How to set some variable to some value when option is present

I am not good at Bash scripting, and trying to learn more. Let me introduce my question with code:
#!/bin/bash
version_num=
isWindows=
MSVC_VER=
VERBOSE=
while getopts “hw:v:m:V” OPTION
do
case $OPTION in
h)
usage
exit 1
;;
w)
isWindows=$OPTARG
;;
v)
version_num=$OPTARG
;;
m)
MSVC_VER=$OPTARG
;;
V)
VERBOSE=1
;;
?)
usage
exit
;;
esac
done
For space, usage function is removed.
My questions are:
First question:
currently, if I use this script, I have to feed parameter values after each option, for example:
`bash test_bash.sh -v 4.2.2.0 -m 10.0 -w 1`
However, assuming that I only need to see whether -w is present, then set some variable value. I don't want to provide -w 1 since 1 is just a flag to do something. I would like the script to work like:
bash test_bash.sh -w -v 4.2.2.0 -m 10.0
How can I achieve this? I would like to do something like rm -r -f, which can have multiple options and does not require that each option is followed by some value.
Second question:
if I remove
V)
VERBOSE=1
;;
and :V from the while line as well as VERBOSE=, this script does not work anymore. Is it because :V is required?
Thanks a lot for your time and help!
Putting a : after a letter in the getopts parameter indicates whether it takes a parameter after it or not. So change to:
while getopts “hwv:m:V” OPTION
Removeing :V from the script breaks it because the : is for the m option that comes before it, not the V option that comes after. When you remove that :, it means that m no longer takes a parameter, but you need that.

Passing variables in arguments

Im looking how to pass an argument in a find file function
this should give all the sh files in my computer first the basename a tab with then all
the directories
For example myBashfile.sh *sh
at the moment i have this:
while getopts b opt
do
case $opt in
b) find / -name $OPTARG -printf "%f\n"-print 2>/dev/null ;;
esac
done
wich gives only the output of
test1.sh
test60.sh
anothertest.sh
but i need as output: (with a tab)
test1.sh /home/directory5/directory6
test60.sh /home/directory50/directory6
anothertest.sh /home/directory5/directory6
can anyone help me please?
A comprehensive answer and the manuals. In brief:
$1 # means the 1st argument
$2 # means the 2nd argument
$# # means all arguments

Resources