from the shellscript one option has been chosen OR none of them - bash

am not expert on bash script and am looking help on. I would like to find a way at the end from the while loop condition that the script can be executed unless one option ([-1|-2|-3]) has been chosen OR none of them.
What the best way to do? I have absolutely no idea how to.
Many Thanks.
#!/bin/bash
echo " $0 [-e <option_e>] [-f <option_f>] [-1|-2|-3] [user#]fqdn"
OPTION1=""
OPTION2=""
OPTION3=""
while (( "$#" )); do
if [ "$1" == "-1" ]; then
OPTION1=1
elif [ "$1" == "-2" ]; then
OPTION2=1
elif [ "$1" == "-3" ]; then
OPTION3=1
fi
shift
done
I forgot to mention. this is a part of the script. further, there are several conditions that can conflict if there more then 2 options chosen.
EDIT: the valid selections either exactly one option or zero, but not two or more!

thank you SamuelKirschner -
I did to add [[ -n $ALL_OPTS ]] && then it works ! I am happy now.
#!/bin/bash
OPTION1=""
OPTION2=""
OPTION3=""
while (( "$#" )); do
if [ "$1" == "-1" ]; then
OPTION1=1
elif [ "$1" == "-2" ]; then
OPTION2=1
elif [ "$1" == "-3" ]; then
OPTION3=1
fi
shift
done
ALL_OPTS="$OPTION1$OPTION2$OPTION3";
echo $ALL_OPTS
if [[ -n $ALL_OPTS ]] && [[ $ALL_OPTS -ge 2 ]];then
echo 'Please provide a maximum of one of the options [-1|-2|-3]' 1>&2
exit 1
fi

You can simply test if all of the OPTION-variables concated have the wrong length with ALL_OPTS="$OPTION1$OPTION2$OPTION3"; test ${#ALL_OPTS} -ge 2 and exit. Where "$OPTION1$OPTION2$OPTION3" is just all of the variables written next to each other, therefor it is "", "1", "11" or "111".
#!/bin/bash
echo " $0 [-e <option_e>] [-f <option_f>] [-1|-2|-3] [user#]fqdn"
OPTION1=""
OPTION2=""
OPTION3=""
while (( "$#" )); do
if [ "$1" == "-1" ]; then
OPTION1=1
elif [ "$1" == "-2" ]; then
OPTION2=1
elif [ "$1" == "-3" ]; then
OPTION3=1
fi
shift
done
ALL_OPTS="$OPTION1$OPTION2$OPTION3";
if test ${#ALL_OPTS} -ge 2; then
echo 'Please provide a maximum of one of the options [-1|-2|-3]' 1>&2
exit 1
fi
From man bash
${#parameter}
Parameter length. The length in characters of the value of parameter is substituted. [...]
Edit: I messed up the condition quite a bit. (original answer was test -z "$OPTION1$OPTION2$OPTION3")

Keeping a count of options might be a possibility. Something like this, using getopts:
#!/usr/bin/env bash
echo "${0##*/} [-e <option_e>] [-f <option_f>] [-1|-2|-3] [user#]fqdn"
count=0
while getopts e:f:123 opt; do
case "$opt" in
e) opt_e="$OPTARG" ;;
f) opt_f="$OPTARG" ;;
1|2|3) declare opt_$opt=true; ((count++)) ;;
esac
done
shift $((OPTIND-1))
# Fail if our counter is too high
((count>1)) && printf 'ERROR: only one digit option allowed.\n' >&2 && exit 1
echo "done"
Alternately, you could keep the numeric options in a variable which you could check using globs:
#!/usr/bin/env bash
echo "${0##*/} [-e <option_e>] [-f <option_f>] [-1|-2|-3] [user#]fqdn"
nopt=""
while getopts e:f:123 opt; do
case "$opt" in
e|f) declare opt_$opt="$OPTARG" ;;
1|2|3) nopt="$nopt$opt" ;;
esac
done
shift $((OPTIND-1))
# Fail if we collected too many digits
((${#nopt}>1)) && printf 'ERROR: only one digit option allowed.\n' >&2 && exit 1
echo "done"
Then, you could switch functionality based on the value of $nopt.

Related

Bash script to test for only presence of flag

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

if condition inside function is not working as desired when function called with command line arguments inside find statement

#!/bin/bash
# Code to generate script usage
if [[ "$#" -ne 1 ]] && [[ "$#" -ne 2 ]]; then
flag=1;
elif ! [[ "$1" == "abcd" || "$1" == "dcba" ]]; then
echo "Invalid"
flag=1;
fi
while [ $# -gt 1 ]
do
case $2 in
'streams')
;;
*)
echo "unrecognised optional arg $2"; flag=1;
;;
esac
shift
done
if [ "$flag" == "1" ]; then
echo "Usage:"
exit
fi
function main {
arg1=$1
streams=$2
if [ "${streams}" == "streams" ]; then
echo entering here
else
echo entering there
fi
}
parent_dir=`pwd`
find $parent_dir -name "*" -type d | while read d; do
cd $denter code here
main $1 $2
done
Why the code does not enter "entering here" when script run with arguments "abcd" and "streams" ?
I feel that function having two arguments is causing the problem, code was working fine with one argument
Several things you might want to fix in your code, before attempts are made to find the specific problem. It is possible that it will disappear after modifying your script accordingly. If the problem is still alive, I'll edit my answer with a solution. If you decide to apply the following changes, please update your code in the question.
Consistent usage of either [[ or [. [[ is a Bash keyword similar to (but more powerful than) the [ command.
See
Bash FAQ 31
Tests And Conditionals
Unless you're writing for POSIX sh, I recommend [[.
Use (( for arithmetic expressions. ((...)) is an arithmetic command, which returns an exit status of 0 if the expression is nonzero, or 1 if the expression is zero. Also used as a synonym for let, if assignments are needed. See Arithmetic Expression.
Use the variable PWD instead of pwd. PWD is a builtin variable in all POSIX shells that contains the current working directory. pwd(1) is a POSIX utility that prints the name of the current working directory to stdout. Unless you're writing for some non-POSIX system, there is no reason to waste time executing pwd(1) rather than just using PWD.
The function keyword is not portable. I suggest you to avoid using it and simply write function_name() { your code here; } # Usage
$parent_dir is not double-quoted. "Double quote" every literal that contains spaces/metacharacters and every expansion: "$var", "$(command "$var")", "${array[#]}", "a & b". See
Quotes
Arguments
ShellCheck your code before uploading.
Replace the while condition logic with an if condition, so that shift is no longer required. Shift was the devil I was facing I found.
#!/bin/bash
# Code to generate script usage
if [[ "$#" -ne 1 ]] && [[ "$#" -ne 2 ]]; then
flag=1;
elif ! [[ "$1" == "abcd" || "$1" == "dcba" ]]; then
echo "Invalid"
flag=1;
fi
#while [[ $# -gt 1 ]]
#do
# case $2 in
# 'streams')
# ;;
# *)
# echo "unrecognised optional arg $2"; flag=1;
# ;;
# esac
# shift
#done
if [[ $2 == "streams" ]]; then
:
elif [[ (-z $2) ]]; then
:
else
echo "unrecognised optional arg $2"; flag=1;
fi
if [[ "$flag" == "1" ]]; then
echo "Usage:"
exit
fi
function main {
streams=$2
if [[ "${streams}" == "streams" ]]; then
echo entering here
else
echo entering there
fi
}
parent_dir=`pwd`
find $parent_dir -name "*" -type d | while read d; do
cd $d
main $1 $2
done

Testing truth/falseness in bash script

For some reason, I can't figure out how to test truth in bash:
#!/bin/bash
FORCE_DELETE=""
BE_VERBOSE=""
OPTIND=1
while getopts ":fv" FLAG "$#" ; do
if [[ "$FLAG" == "f" ]] ; then
FORCE_DELETE="true"
fi
if [[ "$VALUE" == "v" ]] ; then
BE_VERBOSE="true"
fi
if [[ "$FLAG" == "?" ]] ; then
echo "Usage: $0 [-fv] file ..."
exit 1
fi
done
shift `expr $OPTIND - 1`
if [[ "$FORCE_DELETE" == "true" && "BE_VERBOSE" == "true" ]] ; then
echo "FORCE_DELETE AND BE_VERBOSE $#"
elif [[ "$FORCE_DELETE" == "true" ]] ; then
echo "FORCE_DELETE $#"
elif [[ "$BE_VERBOSE" == "true" ]] ; then
echo "BE_VERBOSE $#"
else
echo "$#"
fi
exit 0
Transcript:
$ test a b
a b
$ test -f a b
FORCE_DELETE a b
$ test -v a b
a b
$ test -fv a b
FORCE_DELETE a b
Why does my bash script respond to the -f flag but not the -v flag?
Most likely a typo :
[[ "$VALUE" == "v" ]],
this should be
[[ "$FLAG" == "v" ]]
You specifically ask about testing true/false. These are built in to the language rather than using strings, and you don't need the [[ test. Here is how I would write this:
#!/bin/bash
force_delete=false # Don't use UPPERCASE
be_verbose=false # they could collide with reserved variables
# OPTIND does not need to be initialised
while getopts :fv flag
do
# appears one of your if statements is incorrect
# a case is often used with getopts
case $flag in
f) force_delete=true
;;
v) be_verbose=true
;;
\?) echo "Usage: $0 [-fv] file ..."
exit 1
;;
esac
done
shift $((OPTIND-1)) # don't create a child process for simple arithmetic
if $force_delete && $be_verbose
then
echo "force_delete AND be_verbose $#"
elif $force_delete
then
echo "force_delete $#"
elif $be_verbose
then
echo "be_verbose $#"
else
echo "$#"
fi
# Bash exits 0 by default

How to validate shell arguments?

I am trying to write a simple sh script that must be invoked with 2 arguments:
sh myscript.sh --user "some user" --fizz "buzz"
At the top of myscript.sh I have:
#!/bin/sh
# VALIDATION
# 1. Make sure there are 5 positional arguments (that $4 exists).
die () {
echo >&2 "$#"
exit 1
}
[ "$#" -eq 5 ] || die "5 arguments required, $# provided"
# 2. Make sure $1 is "-u" or "--user".
# 3. Make sure $3 is "-f" or "--fizz".
If validation fails, I'd like to print a simple usage message and then exit the script.
I think I have #1 correct (checking # of positional arguments), but have no clue how to implement #2 and #3. Ideas?
# 2. Make sure $1 is "-u" or "--user".
if ! [ "$1" = -u -o "$1" = --user ]; then
# Test failed. Send a message perhaps.
exit 1
fi
# 3. Make sure $3 is "-f" or "--fizz".
if ! [ "$3" = -f -o "$3" = --fizz ]; then
# Test failed. Send a message perhaps.
exit 1
fi
Other forms for testing a variable for two possible possible values:
[ ! "$var" = value1 -a ! "$var" = value2 ]
[ ! "$var" = value1 ] && [ ! "$var" = value2 ]
! [ "$var" = value1 && ! [ "$var" = value2 ]
For Bash and similarly syntaxed shells:
! [[ $var = value1 || $var = value2 ]]
[[ ! $var = value1 || ! $var = value2 ]]
Besides using negated conditions with if blocks, you can also have positive conditions with ||
true_condition || {
# Failed. Send a message perhaps.
exit 1
}
true_condition || exit 1
Of course && on the other hand would apply with negated conditions.
Using case statements:
case "$var" in
value1|value2)
# Valid.
;;
*)
# Failed.
exit 1
;;
esac
Manually:
if [ -z "$1" ];then
fi
if [ -z "$2" ];then
fi
if [ -z "$3" ];then
fi
...
Or check getopt
while getopts "uf" OPTION
do
case $OPTION in
u)
echo "-u"
;;
f)
echo "-f"
;;
esac
done

how to make sure that N+1 argument is present when Nth argument is equal to "--check"

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

Resources