How do i compare 2 strings in shell? - shell

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

Related

How to pass an function argument inside a switch statement? [duplicate]

This question already has answers here:
Passing parameters to a Bash function
(7 answers)
Closed 3 months ago.
I'm writing the following script and I found the argument is not passed to a function called. However, when in an individual shell I can archive this.
usage() { if [ -n "$error_mess" ]; then echo -e "$error_mess"; fi; echo -e "usage stamens too long to present" 1>&2; exit 1; }
while getopts ":f:" flag
do
case "${flag}" in
f) if [ -n "${OPTARG}" ] ; then file=${OPTARG} ; else error_mess='\033[1m\033[5mError: file cannot be empty\033[0m' | usage; fi;;
*) usage;;
esac
done
I tried to put error_mess section after usage when calling inside the switch statement, but this error message is still not printing out.
When tried directly in bash it is working (only when the first line is run). The code entered directly to shell was:
# To check syntax error
error_mess='\033[1m\033[5mError: file cannot be empty\033[0m' ; if [ -n "$error_mess" ]; then echo -e "$error_mess"; fi
# To check if it can be passed inside a function
usage() { if [ -n "$error_mess" ]; then echo -e "$error_mess"; fi; echo -e "usage statement" 1>&2; exit 1; } ; error_mess='\033[1m\033[5mError: file cannot be empty\033[0m' | usage
# To check if it can be nested in if statement
a=0; if [ $a = 1 ] ; then echo "$a" ; else error_mess='\033[1m\033[5mError: file cannot be empty\033[0m' | usage ; fi
If I'm not doing it right, how should I correct it? I have other use case that do not have an error_mess or is having a different error_mess.
When you do cmd1 | cmd2 in bash, each command is executed in a separate subshell.
When one of the commands is var=value, then that variables only exists in that subshell.
You'll want to remove the pipe
if [ -n "$OPTARG" ]; then
file=$OPTARG
else
error_mess='\033[1m\033[5mError: file cannot be empty\033[0m' usage
fi
Think about passing the error message as an argument to the usage function. Then if you accidentally set the error_mess in one place but call usage in another place, you won't get a spurious error message.
If you're willing to give up the fancy error output:
while getopts "f:" flag # remove leading colon
do
case "${flag}" in
f) file=${OPTARG:?Error: file cannot be empty} ;;
# ...........^^
*) usage;;
esac
done
Then:
$ bash prog.sh -f
prog.sh: option requires an argument -- f
usage ...
$ bash prog.sh -f ""
prog.sh: line 5: OPTARG: Error: file cannot be empty
From the manual
${parameter:?word}
If parameter is null or unset, the expansion of word (or a message to that effect if word is not present) is written to the standard error and the shell, if it is not interactive, exits. Otherwise, the value of parameter is substituted.
To conclude, what you probably want is this:
if [ -n "$OPTARG" ]; then
file=$OPTARG
else
usage 'Error: file cannot be empty'
fi
And then usage looks like:
usage() {
{
[[ "$1" ]] && printf '\033[1m\033[5m%s\033[0m\n' "$1"
echo -e "usage stamens too long to present"
} 1>&2
exit 1
}

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

How to detect if an argument was passed to the script?

I am a novice at shell scripting. I have written a script that takes zero or more options and an optional path parameter. I want to use the current directory if a path parameter is not set.
This is the argument parsing section of the script:
OPTIONS=$(getopt -o dhlv -l drop-databases,help,learner-portal,verifier-portal -- "$#")
if [ $? -ne 0 ]; then
echo "getopt error"
exit 1
fi
eval set -- $OPTIONS
while true; do
case "$1" in
-d|--drop-databases) RESETDB=1
;;
-h|--help) echo "$usage"
exit
;;
-l|--learner-portal) LERPOR=1
;;
-v|--verifier-portal) VERPOR=1
;;
--) shift
break;;
*) echo -e "\e[31munknown option: $1\e[0m"
echo "$usage"
exit 1
;;
esac
shift
done
# Set directory of module
if [[ -n $BASH_ARGV ]]
then
MOD_DIR=$(readlink -f $BASH_ARGV)
fi
if [[ -n $MOD_DIR ]]
then
cd $MOD_DIR
fi
The script works as intended when called without and arguments, or when called with both options and a path.
However, when I run the script and only specify options, I get an error from readlink like so
$ rebuild_module -dv
readlink: invalid option -- 'd'
Try 'readlink --help' for more information.
Obviously, it's parsing the options wrong, but I'm not sure how to detect that I haven't passed a path, and therefore avoid calling readlink. How can I go about correcting this behaviour?
You can do [ $# -ne 0 ] instead of [[ -n $BASH_ARGV ]]. The former is affected by shift/set, but the latter isn't:
$ cat test.sh
echo "$#"
echo "${BASH_ARGV[#]}"
echo "$#"
eval set -- foo bar
shift
echo "$#"
echo "${BASH_ARGV[#]}"
echo "$#"
$ bash test.sh x y z
3
z y x
x y z
1
z y x
bar

How to create a bash script with optional parameters for a flag

I'm trying to create a script which will have a flag with optional options. With getopts it's possible to specify a mandatory argument (using a colon) after the flag, but I want to keep it optional.
It will be something like this:
./install.sh -a 3
or
./install.sh -a3
where 'a' is the flag and '3' is the optional parameter that follows a.
Thanks in advance.
The getopt external program allows options to have a single optional argument by adding a double-colon to the option name.
# Based on a longer example in getopt-parse.bash, included with
# getopt
TEMP=$(getopt -o a:: -- "$#")
eval set -- "$TEMP"
while true ; do
case "$1" in
-a)
case "$2" in
"") echo "Option a, no argument"; shift 2 ;;
*) echo "Option a, argument $2"; shift 2;;
esac ;;
--) shift; break ;;
*) echo "Internal error!"; exit 1 ;;
esac
done
The following is without getopt and it takes an optional argument with the -a flag:
for WORD; do
case $WORD in
-a?) echo "single arg Option"
SEP=${WORD:2:1}
echo $SEP
shift ;;
-a) echo "split arg Option"
if [[ ${2:0:1} != "-" && ${2:0:1} != ""]] ; then
SEP=$2
shift 2
echo "arg present"
echo $SEP
else
echo "optional arg omitted"
fi ;;
-a*) echo "arg Option"
SEP=${WORD:2}
echo $SEP
shift ;;
-*) echo "Unrecognized Short Option"
echo "Unrecognized argument"
;;
esac
done
Other options/flags also can be added easily.
Use the getopt feature. On most systems, man getopt will yield documentation for it, and even examples of using it in a script. From the man page on my system:
The following code fragment shows how one might process the arguments
for a command that can take the options -a and -b, and the option -o,
which requires an argument.
args=`getopt abo: $*`
# you should not use `getopt abo: "$#"` since that would parse
# the arguments differently from what the set command below does.
if [ $? != 0 ]
then
echo 'Usage: ...'
exit 2
fi
set -- $args
# You cannot use the set command with a backquoted getopt directly,
# since the exit code from getopt would be shadowed by those of set,
# which is zero by definition.
for i
do
case "$i"
in
-a|-b)
echo flag $i set; sflags="${i#-}$sflags";
shift;;
-o)
echo oarg is "'"$2"'"; oarg="$2"; shift;
shift;;
--)
shift; break;;
esac
done
echo single-char flags: "'"$sflags"'"
echo oarg is "'"$oarg"'"
This code will accept any of the following as equivalent:
cmd -aoarg file file
cmd -a -o arg file file
cmd -oarg -a file file
cmd -a -oarg -- file file
In bash there is some implicit variable:
$#: contains number of arguments for a called script/function
$0: contains names of script/function
$1: contains first argument
$2: contains second argument
...
$n: contains n-th argument
For example:
#!/bin/ksh
if [ $# -ne 2 ]
then
echo "Wrong number of argument - expected 2 : $#"
else
echo "Argument list:"
echo "\t$0"
echo "\t$1"
echo "\t$2"
fi
My solution:
#!/bin/bash
count=0
skip=0
flag="no flag"
list=($#) #put args in array
for arg in $# ; do #iterate over array
count=$(($count+1)) #update counter
if [ $skip -eq 1 ]; then #check if we have to skip this args
skip=0
continue
fi
opt=${arg:0:2} #get only first 2 chars as option
if [ $opt == "-a" ]; then #check if option equals "-a"
if [ $opt == $arg ] ; then #check if this is only the option or has a flag
if [ ${list[$count]:0:1} != "-" ]; then #check if next arg is an option
skip=1 #skip next arg
flag=${list[$count]} #use next arg as flag
fi
else
flag=${arg:2} #use chars after "-a" as flag
fi
fi
done
echo $flag

Converting Bash command line options to variable name

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

Resources