Bash: default boolean value in getopts - bash

I want to include a default option in my script where if the user uses this option, set the flag to true or it should be false by default. It seems that the script is not accepting false or true as boolean value. How can I make it boolean?
flag=
instructions() {
echo " -a File name" >&2
echo " -f optional boolean" flag=${flag:-false}
}
while getopts ":a:fi" option; do
case "$option" in
a ) file=$OPTARG;;
f ) flag=true;;
u )
instructions
;;
\?)
echo "Not valid -$OPTARG" >&2
instructions
;;
: ) echo "args required";;
esac
done
if [[ "$flag" != true || "$flag" != false ]]; then
echo "Not a boolean value"
fi

Check this, I made some fixes to your script (commented in the code) along with a proper formatting.
#!/bin/bash
# Set the default value of the flag variable
flag=false
instructions() {
echo "Usage: $0 [ -a FILE ] [ -f ]" >&2
echo " -a File name" >&2
echo " -f optional boolean flag=${flag:-false}" >&2
}
# If the script must be executed with options, this checks if the number of arguments
# provided to the script is greater than 0
if [ $# -eq 0 ]; then
instructions
exit 1
fi
while getopts ":a:fi" option; do
case "${option}" in
a )
file="$OPTARG"
;;
f )
flag=true
;;
i ) # "u" is not a valid option
instructions
exit 0
;;
\?)
echo "Option '-$OPTARG' is not a valid option." >&2
instructions
exit 1
;;
: )
echo "Option '-$OPTARG' needs an argument." >&2
instructions
exit 1
;;
esac
done
# Since a variable can't have 2 values assigned at the same time,
# you should use && (and) instead of || (or)
if [[ "$flag" != true ]] && [[ "$flag" != false ]]; then
echo "Not a boolean value"
fi
exit 0

Related

Two ways to handle short and long options in bash

Have written two ways, trying to parse short and long options by hand. The first is complicated with the use of IFS=" =". Perhaps set -- $* is not necessary.
rando ()
{
IFSPREV="$IFS" # Save IFS (splits arguments on whitespace by default)
IFS=" =" # Split arguments on " " and "="
set -- $* # Set positional parameters to command line arguments
IFS="$IFSPREV" # Set original IFS
local iarg=0 narg="$#"
while (( narg > 0 )); do
opt="$1"
iarg=$(( iarg + 1 ))
case $opt in
("-s"|"--src"|"--source") src="$2" ; shift 2 ;;
("-d"|"--dst"|"--destin") dst="$2" ; shift 2 ;;
("--") shift 1 ; break ;;
("-"*) printf '%s\n' "Unknown option: $1" ; shift 1 ;;
(*) shift 1 ; break ;;
esac
done
}
And here is the other, but I also want to split on = for long-options.
rando ()
{
PARAMS=""
while (( "$#" )); do
case "$1" in
("-s"|"--src"|"--source")
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
src="$2"
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
("-d"|"--dst"|"--destin")
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
dst="$2"
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
-*|--*=) # unsupported flags
echo "Error: Unsupported flag $1" >&2
exit 1
;;
*) # preserve positional arguments
PARAMS="$PARAMS $1"
shift
;;
esac
done
}

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)$

how to make an argument optional in getopt bash

I want to make an argument as optional in getopt bash so that if the user didn't specify it, then it still runs without killing the program. How can i do that. Here is my previous code
while getopts ":l:q:s:e:hg:" opt; do
case $opt in
l)
lincRNAfasta=$OPTARG
;;
q)
query_species=$OPTARG
;;
s)
subject_species=$OPTARG
;;
e)
subject_gff=$OPTARG
;;
h)
echo "USAGE : open script in text editor"
exit 1
;;
g)
subject_genome=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
Sorry, had to update my answer, I misunderstood the question.
I was looking at the documentation for getopts.c which does support ::
But the code that you have works whether I specify 1, 2, or 3 arguments, and in any order.
If it is exiting with an error one reason could be that the variables need to be defined:
#! /bin/bash
lincRNAfasta=
query_species=
subject_species=
subject_gff=
subject_genome=
help='''
USAGE : open script in text editor
-l lincRNAfasta
-q query_species
-s subject_species
-e subject_gff
-g subject_genome
'''
while getopts ":l:q:s:e:hg:" opt; do
case $opt in
l)
lincRNAfasta=$OPTARG
;;
q)
query_species=$OPTARG
;;
s)
subject_species=$OPTARG
;;
e)
subject_gff=$OPTARG
;;
h)
printf "$help"
exit 1
;;
g)
subject_genome=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
echo "doing something else"
if [ -z "$lincRNAfasta" ];then
echo "its empty"
echo "lincRNAfasta: $lincRNAfasta"
echo
else
echo "not empty"
echo "lincRNAfasta: $lincRNAfasta"
echo
fi
if [ -z "$query_species" ];then
echo "its empty"
echo "query_species: $query_species"
echo
else
echo "not empty"
echo "query_species: $query_species"
echo
fi
if [ -z "$subject_species" ];then
echo "its empty"
echo "subject_species: $subject_species"
echo
else
echo "not empty"
echo "subject_species: $subject_species"
echo
fi
if [ -z "$subject_gff" ];then
echo "its empty"
echo "subject_gff: $subject_gff"
echo
else
echo "not empty"
echo "subject_gff: $subject_gff"
echo
fi
if [ -z "$subject_genome" ];then
echo "its empty"
echo "subject_genome: $subject_genome"
echo
else
echo "not empty"
echo "subject_genome: $subject_genome"
echo
fi
# do something once variables have been set
# any variable not set will be empty
echo "doing something else"
Output:
bob#squids:~/Desktop$ ./1.sh -h
USAGE : open script in text editor
-l lincRNAfasta
-q query_species
-s subject_species
-e subject_gff
-g subject_genome
bob#squids:~/Desktop$ ./1.sh -s a -g h -e e -q q
doing something else
its empty
lincRNAfasta:
not empty
query_species: q
not empty
subject_species: a
not empty
subject_gff: e
not empty
subject_genome: h
doing something else
bob#squids:~/Desktop$ ./1.sh -s a -g h -e e
doing something else
its empty
lincRNAfasta:
its empty
query_species:
not empty
subject_species: a
not empty
subject_gff: e
not empty
subject_genome: h
doing something else
bob#squids:~/Desktop$ ./1.sh -s a -e e -g 123
doing something else
its empty
lincRNAfasta:
its empty
query_species:
not empty
subject_species: a
not empty
subject_gff: e
not empty
subject_genome: 123
doing something else
Or instead of initializing the variables as empty, you could initialize them as a string like "NOTSPECIFIEDONSTART". And when starting the script you could pass an empty string like -g ''

Getopts in bash not selecting option

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`

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