How to validate shell arguments? - shell

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

Related

bash - passing optional arguments to script - parametername + string_value

i use a script that accepts parameter. parameters are optional and may occur in any order.
#!/bin/bash
# script name: test.sh
for var in "$#"
do
if [ ! -z "$var" ] && ([ $var = "--example" ] || [ $var = "-e" ]); then
echo "example"
elif [ ! -z "$var" ] && ([ $var = "--project" ] || [ $var = "-p" ]); then
echo "project with string xxxxxxx"
fi
done
in this simple example, you could call it like follows (some examples):
# this will echo example
./test.sh --example
# this will echo project with string xxxxxxx
./test.sh --project
# this will echo both example and project with string xxxxxxx
./test.sh --example --project
NOW, what i want to achieve is that i can do something like this (warning, this is pseuco code):
#!/bin/bash
# script name: test.sh
for var in "$#"
do
if [ ! -z "$var" ] && ([ $var = "--example" ] || [ $var = "-e" ]); then
echo "example"
elif [ ! -z "$var" ] && ([ $var = "--project" ] || [ $var = "-p" ]); then
echo "project with string $VAR_VALUE"
fi
done
# this will echo example
./test.sh --example
# this will echo project with string myproject1
./test.sh --project="myproject1"
# this will echo both example and project with string myproject2
./test.sh --example --project="myproject2"
can someone help me rewrite it so this will work somehow?
Use getopt. It handles short and long options, allows for both --long value and --long=value, decomposes -abc into -a -b -c, understands -- to end option parsing, and more.
#!/bin/bash
args=$(getopt -o ep: -l example,project: -n "$0" -- "$#") || exit
eval set -- "$args"
while [[ $1 != '--' ]]; do
case "$1" in
-e|--example) echo "example"; shift 1;;
-p|--project) echo "project = $2"; shift 2;;
# shouldn't happen unless we're missing a case
*) echo "unhandled option: $1" >&2; exit 1;;
esac
done
shift # skip '--'
echo "remaining non-option arguments: $#"
There are two possible path toward parsing argument list
Build custom option parser
use getopt, using 'long options'
The first approach is relatively simple (at this time). Using case instead of if to handle variants:
last_arg=
for arg in "$#"
do
if [ "$last_arg" = "-p" ] ; then
VAR_VALUE=$arg ;
last_arg=
echo "project with string $VAR_VALUE"
continue
fi
case "$arg" in
-e | --example)
echo "example" ;;
-p)
last_arg=$arg ;;
--project=*)
VAR_VALUE=${arg#*=}
echo "project with string $VAR_VALUE" ;;
*) ERROR-MESSAGE ;;
esac
done
exit
The BETTER approach is to leverage existing code. In particular getopt, which can handle long options:
#! /bin/bash
if T=$(getopt -o ep: --long 'example,project:' -n ${0#*/} -- "$#") ; then
eval set -- "$T"
else
exit $?
fi
while [ "$#" -gt 0 ] ; do
case "$1" in
-e | --example)
echo "example"
;;
-p | --project)
shift
VAR_VALUE=$1
echo "project with string $VAR_VALUE"
;;
--)
break
;;
*) echo "ERROR:$1" ;;
esac
shift
done

if [-z $var1 ] && [$var2 == "1" ]; then not working as expected [duplicate]

This question already has answers here:
Getting "command not found" error while comparing two strings in Bash
(4 answers)
Closed 3 years ago.
I'm writing a bash script where i need to combine two conditions with && operator
var1=value
var2=1
if [-z $var1 ] && [$var2=="1"]; then
do something
else
do something else
fi
but it always executes the else part.
My research
google gave me bash conditions like this but its not working for me.
if [condition1] && [condition2]; then
do something
fi
Another method i tried is this but it still completely ignored the true part
if [[-z $var1 ]] && [$var2=="1"]; then
do something
else
do something else
fi
Tried with -a operator like this
if [-z $var1 -a $var2=="1" ]; then
tried nested if
if [-z $var1 ]; then
if [$var2=="1"]; then
do something
fi
else
do something else
fi
So i know i am doing something wrong but my goal is i want to check for any value in $var1 and also want $var2 condition to be true and execute the true part.
UPDATE
i tried this
#! /bin/bash
set -o nounset
#set -o errexit
set -o pipefail
set -o xtrace
var1=pop
var2=1
if test -z "$var1" && test "$var2" -eq 1; then
echo Y1
fi
if [ -z "$var1" ] && [ "$var2" -eq 1 ]; then
echo Y2
fi
and this is the output
root#c847b6423295:/# ./test.sh
+ var1=pop
+ var2=1
+ test -z pop
+ '[' -z pop ']'
root#c847b6423295:/#
What am i doing wrong ?
You want to encode "$var1 is not empty and $var2 is equal to 1", you can do:
if test -z "$var1" && test "$var2" -eq 1; then
echo Y
fi
which is equivalent to:
if [ -z "$var1" ] && [ "$var2" -eq 1 ]; then
echo Y
fi
If you want to compare strings, use a single equals sign:
if test "aaa" = "aaa"; then
echo Y
fi

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

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.

How to pass a long option to a bash script?

./script.sh -abc hello
How can I write my script to use '-abc' as the option and 'hello' as the value to that option?
I should be able to pass this value to all the functions in this script. Lets say I have 2 functions: X and Y.
Use this in your script:
[[ $1 == -abc ]] && value="$2" || echo invalid option
If you don't want to print any messages on wrong option or no option, then omit the || echo ... part, value will be empty.
If you want to make the second argument a must, then:
[[ $1 == -abc ]] && [[ $2 != "" ]] && value="$2" || echo invalid option
Using if else loop will give you complete control over this:
if [[ $1 == -abc ]]; then
#if first option is valid then do something here
if [[ $2 != "" ]]; then
value="$2"
else
#if second option is not given then do something here
echo invalid option
fi
else
echo invalid option
#if first option is invalid then do something here
fi
If you want to make the first argument a must too, then change the first if statement line to
if [[ $1 == -abc && $1 != "" ]]; then
If you want to pass as many arguments as you wish and process them,
then use something like this:
#!/bin/bash
opts=( "$#" )
#if no argument is passed this for loop will be skipped
for ((i=0;i<$#;i++));do
case "${opts[$i]}" in
-abc)
# "${opts[$((i+1))]}" is the immediately follwing option
[[ "${opts[$((i+1))]}" != "" ]] &&
value="${opts[$((i+1))]}"
echo "$value"
((i++))
#skips the nex adjacent argument as it is already taken
;;
-h)
#dummy help option
echo "Options are [-abc value], -h"
;;
*)
#other unknown options
echo invalid option
break
;;
esac
done
This is an example of handling multiple arguments with only two options available -abc value and -h
bash doesn't have a built in command for processing long arguments. In order to parse long options in a shell script, you'll need to iterate over the arguments list yourself.
Here's one approach:
#!/bin/sh
is_option_arg () {
case $1 in
-*)
return 1
;;
*)
return 0
;;
esac
}
usage () {
echo "$(basename "$0") -abc ARG -def ARG -verbose"
}
OPT_ABC=
OPT_DEF=
OPT_VERBOSE=false
while [ "$#" -gt 0 ]; do
case $1 in
-abc)
shift
{ [ "$#" -ne 0 ] && is_option_arg "$1"; } || { usage >&2; exit 1; }
OPT_ABC=$1
;;
-def)
shift
{ [ "$#" -ne 0 ] && is_option_arg "$1"; } || { usage >&2; exit 1; }
OPT_DEF=$1
;;
-verbose)
OPT_VERBOSE=true
;;
*)
break
;;
esac
shift
done
echo "OPT_ABC=$OPT_ABC"
echo "OPT_DEF=$OPT_DEF"
echo "OPT_VERBOSE=$OPT_VERBOSE"
if [ "$#" -gt 0 ]; then
echo "Remaining args:"
for arg in "$#"; do
echo "$arg"
done
fi
You pretty much have to implement it yourself manually. Here's one way:
abc=
while [[ "$1" == -* ]]; do
opt=$1
shift
case "$opt" in
-abc)
if (( ! $# )); then
echo >&2 "$0: option $opt requires an argument."
exit 1
fi
abc="$1"
shift
;;
*)
echo >&2 "$0: unrecognized option $opt."
exit 2
;;
esac
done
echo "abc is '$abc', remaining args: $*"
Some sample runs of the above:
(0)$ ./script.sh
abc is '', remaining args:
(0)$ ./script.sh hello
abc is '', remaining args: hello
(0)$ ./script.sh -abc hello
abc is 'hello', remaining args:
(0)$ ./script.sh -abc hello there
abc is 'hello', remaining args: there
(0)$ ./script.sh -abc
./script.sh: option -abc requires an argument.
(1)$ ./script.sh -bcd
./script.sh: unrecognized option -bcd.
(2)$

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

Resources