I have a bash script that I need to take in a user name with a flag, and then I want to be able to look for -r or -w to indicate whether this should be a read or write.
Currently I am using get opts, but this requires that an actual argument be passed to -r and -w.
How do I test if just -r or -w is there without passing something to those flags.
Currently my script looks like this:
#!/bin/bash
while getopts :u:r:w: opt; do
case $opt in
u ) user="$OPTARG" ;;
r ) my_read=1 ;;
w ) my_write=1 ;;
\? ) echo "${0##*/}" [ -erw ]; exit 1 ;;
esac
done
if [[ ${my_write} -eq 1 ]] ; then
echo "write"
fi
if [[ ${my_read} -eq 1 ]] ; then
echo "read"
fi
As noted in the comments, a colon (:) indicates the preceding option character requires an argument. Just remove the colons:
#!/bin/bash
while getopts u:rw opt; do
case $opt in
u ) user="$OPTARG" ;;
r ) my_read=1 ;;
w ) my_write=1 ;;
\? ) echo "${0##*/} [ -erw ]" >&2; exit 1 ;;
esac
done
shift $((OPTIND-1))
if [[ "${my_write}" -eq 1 ]] ; then
echo "write"
fi
if [[ "${my_read}" -eq 1 ]] ; then
echo "read"
fi
Other changes made: quotes on final case moved to include square brackets, output to standard error (>&2) to avoid getting piped inappropriately, the shift line was added so your argument list ($# and $1, etc) have the getopts-parsed options removed, and quotes were placed around tests because otherwise the shell can complain about being passed empty tests (it'll see [[ -eq 1]] if either variable is undefined, which will happen if either -r or -w is not passed, and that is invalid while [[ "" -eq 1 ]] will simply evaluate as false).
Just get them as parameters not options:
while [ -n "$1" ]; do
case "$1" in
-r) echo "read";;
-w) echo "write";;
esac
shift
done
Related
I have a bash script which accepts three command line arguments, e.g script is executed like this: script -c <value> -h <value> -w <value>. I would like to ensure that:
order of arguments is not important
if argument does not have a value, then error message is printed
if any of the arguments are missing, then error message is printed
if there are unknown arguments, then error message is printed
I accomplished this with following case statements:
#!/bin/bash
while :; do
case "$1" in
-h)
[[ x${2%%-*} != x ]] || { echo "Value for "$1" missing!"; exit 1; }
host="$2"
shift 2
;;
-w)
[[ x${2%%-*} != x ]] || { echo "Value for "$1" missing!"; exit 1; }
warning="$2"
shift 2
;;
-c)
[[ x${2%%-*} != x ]] || { echo "Value for "$1" missing!"; exit 1; }
critical="$2"
shift 2
;;
"")
[[ $host && $warning && $critical ]] || { echo "One of the arguments is missing!"; exit 1; }
break
;;
*)
echo "Unknow option"
exit 1
;;
esac
done
However, maybe case itself has some advanced options which could avoid all those [[ ]] tests? Or maybe I should use another method altogether for processing command line arguments if I want to make sure that corner cases described above are also covered?
You should ideally use the getopts builtin for this, though there are other ways as well. Getopts is the most portable and legible option, handling all of your "corner cases" pretty much automatically.
while getopts c:h:w: arg; do
case $arg in
( c ) critical="$OPTARG" ;;
( h ) host="$OPTARG" ;;
( w ) warning="$OPTARG" ;;
( \? ) exit 2 ;;
esac
done
shift $((OPTIND-1))
if [ -z "$critical" ] || [ -z "$host" ] || [ -z "$warning" ]; then
echo "One of the arguments is missing!"
exit 1
fi
Each option is followed by a colon, which indicates it has a mandatory argument. If you have a flag that does not use an argument, do not follow the option with a colon. POSIX getopts does not support options with optional arguments.
See also my answer to this question about supporting long options, which merely keys on the - option whose argument is parsed by a nested case switch. Long options implemented in this manner can actually support optional arguments.
I'm a big fan of overloading -h for help. Assuming you have a help function, put this before the getopts loop:
# when the sole argument is -h, provide help
if [ "$*" = "-h" ]; then
help
exit 0
fi
I have one problem , when i select one option , for exemple ./test.sh -f it should print "mel" but it reads all code.
How does it enter the if condition and passes with other argument ?
if getopts :f:d:c:v: arg ; then
if [[ "${arg}" == d ]] ; then
d_ID=$OPTARG
eval d_SIZE=\$$OPTIND
else
echo "Option -d argument missing: needs 2 args"
echo "Please enter two args: <arg1> <arg2>"
read d_ID d_SIZE
echo "disc $d_ID $d_SIZE" >> $FILENAME
fi
if [[ "${arg}" == c ]] ; then
c_NOME="$OPTARG"
eval c_ID1=\$$OPTIND
eval c_ID2=\$$OPTINDplus1
eval c_FICHEIRO=\$$OPTINDplus2
else
echo "Option -c argument missing: needs 4 args"
echo "Please enter two args: <arg1> <arg2> <arg3> <agr4>"
read c_NOME c_ID1 c_ID2 c_FICHEIRO
echo "raidvss $c_NOME $c_ID1 $c_ID2 $c_FICHEIRO" >> $FILENAME
fi
if [[ "${arg}" == f ]] ; then
echo "mel"
fi
fi
You are using getopts parameters wrong.
if getopts :f:d:c:v: arg
means that -f will follow the value of parameter, like
-f 5
If you want just have -f (without value) you need to change it to
if getopts :fd:c:v: arg ; then
(I deleted the ':'). Also, I think you should better use while cycle and case statements.
See this example
while getopts fd:c:v: opt
do
case "$opt" in
f) echo "mel";;
d) discFunction "$OPTARG";;
c) otherFunction "$OPTARG";;
v) nop;;
\?) echo "$USAGE" >&2; exit 2;;
esac
done
shift `expr $OPTIND - 1`
I am trying to write code to check if any argument (on position N) is equal to "--check" and, if its true, require that next argument (position N+1) is present. Otherwise, exit.
How can i achieve that?
i am trying sth like this but it doesnt seem to work:
i am reiterating arguments and if "--check" is found then setting FLAG to 1 which triggers another conditional check for nextArg:
FLAG=0
for i in "$#"; do
if [ $FLAG == 1 ] ; then
nextARG="$i"
FLAG=0
fi
if [ "$i" == "--check" ] ; then
FLAG=1
fi
done
if [ ! -e $nextARG ] ; then
echo "nextARG not found"
exit 0
fi
I would go with getopts. The link shows an example how you could check for your missing parameter.
You could use a form like this. I use it as a general approach when parsing arguments. And I find it less confusing than using getopts.
while [[ $# -gt 0 ]]; do
case "$1" in
--option)
# do something
;;
--option-with-arg)
case "$2" in)
check_pattern)
# valid
my_opt_arg=$2
;;
*)
# invalid
echo "Invalid argument to $1: $2"
exit 1
;;
esac
# Or
if [[ $# -ge 2 && $2 == check_pattern ]]; then
my_opt_arg=$2
else
echo "Invalid argument to $1: $2"
exit 1
fi
shift
;;
*)
# If we don't have default argument types like files. If that is the case we could do other checks as well.
echo "Invalid argument: $1"
# Or
case $1 in
/*)
# It's a file.
FILES+=("$1")
;;
*)
# Invalid.
echo "Invalid argument: $1"
exit 1
;;
esac
esac
shift
done
I am trying to write a bash script that takes in an option.
Lets call these options A and B.
In the script A and B may or may not be defined as variables.
I want to be able to check if the variable is defined or not.
I have tried the following but it doesn't work.
if [ ! -n $1 ]; then
echo "Error"
fi
Thanks
The "correct" way to test whether a variable is set is to use the + expansion option. You'll see this a lot in configure scripts:
if test -s "${foo+set}"
where ${foo+set} expands to "set" if it is set or "" if it's not. This allows for the variable to be set but empty, if you need it. ${foo:+set} additionally requires $foo to not be empty.
(That $(eval echo $a) thing has problems: it's slow, and it's vulnerable to code injection (!).)
Oh, and if you just want to throw an error if something required isn't set, you can just refer to the variable as ${foo:?} (leave off the : if set but empty is permissible), or for a custom error message ${foo:?Please specify a foo.}.
You did not define how these options should be passed in, but I think:
if [ -z "$1" ]; then
echo "Error"
exit 1
fi
is what you are looking for.
However, if some of these options are, err, optional, then you might want something like:
#!/bin/bash
USAGE="$0: [-a] [--alpha] [-b type] [--beta file] [-g|--gamma] args..."
ARGS=`POSIXLY_CORRECT=1 getopt -n "$0" -s bash -o ab:g -l alpha,beta:,gamma -- "$#"`
if [ $? -ne 0 ]
then
echo "$USAGE" >&2
exit 1
fi
eval set -- "$ARGS"
unset ARGS
while true
do
case "$1" in
-a) echo "Option a"; shift;;
--alpha) echo "Option alpha"; shift;;
-b) echo "Option b, arg '$2'"; shift 2;;
--beta) echo "Option beta, arg '$2'"; shift 2;;
-g|--gamma) echo "Option g or gamma"; shift;;
--) shift ; break ;;
*) echo "Internal error!" ; exit 1 ;;
esac
done
echo Remaining args
for arg in "$#"
do
echo '--> '"\`$arg'"
done
exit 0
Don't do it that way, try this:
if [[ -z $1 ]]; then
echo "Error"
fi
The error in your version is actually the lack of quoting.
Should be:
if [ ! -n "$1" ]; then
echo "Error"
fi
But you don't need the negation, use -z instead.
If you work on Bash, then use double brackets [[ ]] too.
from the man bash page:
-z string
True if the length of string is zero.
-n string
True if the length of string is non-zero.
Also, if you use bash v4 or greater (bash --version) there's -v
-v varname
True if the shell variable varname is set (has been assigned a value).
The trick is "$1", i.e.
root#root:~# cat auto.sh
Usage () {
echo "error"
}
if [ ! -n $1 ];then
Usage
exit 1
fi
root#root:~# bash auto.sh
root#root:~# cat auto2.sh
Usage () {
echo "error"
}
if [ ! -n "$1" ];then
Usage
exit 1
fi
root#root:~# bash auto2.sh
error
I want the user to input something at the command line either -l or -e.
so e.g. $./report.sh -e
I want an if statement to split up whatever decision they make so i have tried...
if [$1=="-e"]; echo "-e"; else; echo "-l"; fi
obviously doesn't work though
Thanks
I use:
if [[ "$1" == "-e" ]]; then
echo "-e"
else
echo "-l";
fi
However, for parsing arguments, getopts might make your life easier:
while getopts "el" OPTION
do
case $OPTION in
e)
echo "-e"
;;
l)
echo "-l"
;;
esac
done
If you want it all on one line (usually it makes it hard to read):
if [ "$1" = "-e" ]; then echo "-e"; else echo "-l"; fi
You need spaces between the square brackets and what goes inside them. Also, just use a single =. You also need a then.
if [ $1 = "-e" ]
then
echo "-e"
else
echo "-l"
fi
The problem specific to -e however is that it has a special meaning in echo, so you are unlikely to get anything back. If you try echo -e you'll see nothing print out, while echo -d and echo -f do what you would expect. Put a space next to it, or enclose it in brackets, or have some other way of making it not exactly -e when sending to echo.
If you just want to print which parameter the user has submitted, you can simply use echo "$1". If you want to fall back to a default value if the user hasn't submitted anything, you can use echo "${1:--l} (:- is the Bash syntax for default values). However, if you want really powerful and flexible argument handling, you could look into getopt:
params=$(getopt --options f:v --longoptions foo:,verbose --name "my_script.sh" -- "$#")
if [ $? -ne 0 ]
then
echo "getopt failed"
exit 1
fi
eval set -- "$params"
while true
do
case $1 in
-f|--foo)
foobar="$2"
shift 2
;;
-v|--verbose)
verbose='--verbose'
shift
;;
--)
while [ -n "$3" ]
do
targets[${#targets[*]}]="$2"
shift
done
source_dir=$(readlink -fn -- "$2")
shift 2
break
;;
*)
echo "Unhandled parameter $1"
exit 1
;;
esac
done
if [ $# -ne 0 ]
then
error "Extraneous parameters." "$help_info" $EX_USAGE
fi