bash if statement with strings always evaluates to true - bash

I am getting started with bash and am having trouble with if statements.
Why does the following script:
#!/bin/bash
read C
if (( $C == 'Y' )) ; then
echo "YES"
elif (( $C == 'N' )) ; then
echo "NO"
fi
Seem to print YES no matter what value $C takes on.

Strings inside the arithmetic statement ((...)) are recursively expanded until you either get an integer value (including 0 for an undefined parameter) or a string that causes a syntax error. Some examples:
# x expands to y, and y expands to 3
$ x=y y=3
$ (( x == 3 )) && echo true
true
$ x="foo bar"
$ (( x == 3 ))
bash: ((: foo bar: syntax error in expression (error token is "bar")
# An undefined parameter expands to 0
$ unset x
$ (( x == 0 )) && echo true
true
In your case, $C expands to some undefined parameter name, and both it and Y expand to 0, and 0 == 0.
For string comparison, use [[ ... ]] instead.
if [[ $C == Y ]]; then

Yep, as #larsks mentioned, you need the square brackets. Try this full version:
#!/bin/bash
read C
if [[ ${C} == 'Y' ]]; then
echo "YES"
elif [[ ${C} == 'N' ]]; then
echo "NO"
fi

Here is the right format.
#!/bin/bash
read C
if [[ $C == 'Y' ]]
then
echo "YES"
elif [[ $C == 'N' ]]
then
echo "NO"
fi

Related

How can I use the ternary operator in the bash script? [duplicate]

Is there a way to do something like this
int a = (b == 5) ? c : d;
using Bash?
ternary operator ? : is just short form of if/else
case "$b" in
5) a=$c ;;
*) a=$d ;;
esac
Or
[[ $b = 5 ]] && a="$c" || a="$d"
Code:
a=$([ "$b" == 5 ] && echo "$c" || echo "$d")
If the condition is merely checking if a variable is set, there's even a shorter form:
a=${VAR:-20}
will assign to a the value of VAR if VAR is set, otherwise it will assign it the default value 20 -- this can also be a result of an expression.
This approach is technically called "Parameter Expansion".
if [[ $b -eq 5 ]]; then a="$c"; else a="$d"; fi
The cond && op1 || op2 expression suggested in other answers has an inherent bug: if op1 has a nonzero exit status, op2 silently becomes the result; the error will also not be caught in -e mode. So, that expression is only safe to use if op1 can never fail (e.g., :, true if a builtin, or variable assignment without any operations that can fail (like division and OS calls)).
Note the "" quotes. They will prevent translation of all whitespace into single spaces.
Double square brackets as opposed to single ones prevent incorrect operation if $b is equal to a test operator (e.g. "-z"; a workaround with [ is [ "x$b" == "xyes" ] and it only works for string comparison); they also lift the requirement for quoting.
(( a = b==5 ? c : d )) # string + numeric
[ $b == 5 ] && { a=$c; true; } || a=$d
This will avoid executing the part after || by accident when the code between && and || fails.
We can use following three ways in Shell Scripting for ternary operator :
[ $numVar == numVal ] && resVar="Yop" || resVar="Nop"
Or
resVar=$([ $numVar == numVal ] && echo "Yop" || echo "Nop")
Or
(( numVar == numVal ? (resVar=1) : (resVar=0) ))
Update: Extending the answer for string computations with below ready-to-run example. This is making use of second format mentioned above.
$ strVar='abc';resVar=$([[ $strVar == 'abc' ]] && echo "Yop" || echo "Nop");echo $resVar
Yop
$ strVar='aaa';resVar=$([[ $strVar == 'abc' ]] && echo "Yop" || echo "Nop");echo $resVar
Nop
The let command supports most of the basic operators one would need:
let a=b==5?c:d;
Naturally, this works only for assigning variables; it cannot execute other commands.
Here is another option where you only have to specify the variable you're assigning once, and it doesn't matter whether what your assigning is a string or a number:
VARIABLE=`[ test ] && echo VALUE_A || echo VALUE_B`
Just a thought. :)
There's also a very similar but simpler syntax for ternary conditionals in bash:
a=$(( b == 5 ? 123 : 321 ))
The following seems to work for my use cases:
Examples
$ tern 1 YES NO
YES
$ tern 0 YES NO
NO
$ tern 52 YES NO
YES
$ tern 52 YES NO 52
NO
and can be used in a script like so:
RESULT=$(tern 1 YES NO)
echo "The result is $RESULT"
tern
#!/usr/bin/env bash
function show_help()
{
ME=$(basename "$0")
IT=$(cat <<EOF
Returns a ternary result
usage: BOOLEAN VALUE_IF_TRUE VALUE_IF_FALSE
e.g.
# YES
$ME 1 YES NO
# NO
$ME 0 YES NO
# NO
$ME "" YES NO
# YES
$ME "STRING THAT ISNT BLANK OR 0" YES NO
# INFO contains NO
INFO=\$($ME 0 YES NO)
EOF
)
echo "$IT"
echo
exit
}
if [ "$1" = "help" ] || [ "$1" = '?' ] || [ "$1" = "--help" ] || [ "$1" = "h" ]; then
show_help
fi
if [ -z "$3" ]
then
show_help
fi
# Set a default value for what is "false" -> 0
FALSE_VALUE=${4:-0}
function main
{
if [ "$1" == "$FALSE_VALUE" ] || [ "$1" = '' ]; then
echo $3
exit;
fi;
echo $2
}
main "$1" "$2" "$3"
Here's a general solution, that
works with string tests as well
feels rather like an expression
avoids any subtle side effects when the condition fails
Test with numerical comparison
a=$(if [ "$b" -eq 5 ]; then echo "$c"; else echo "$d"; fi)
Test with String comparison
a=$(if [ "$b" = "5" ]; then echo "$c"; else echo "$d"; fi)
(ping -c1 localhost&>/dev/null) && { echo "true"; } || { echo "false"; }
You can use this if you want similar syntax
a=$(( $((b==5)) ? c : d ))
Some people have already presented some nice alternatives. I wanted to get the syntax as close as possible, so I wrote a function named ?.
This allows for the syntax:
[[ $x -eq 1 ]]; ? ./script1 : ./script2
# or
? '[[ $x -eq 1 ]]' ./script1 : ./script2
In both cases, the : is optional. All arguments that have spaces, the values must be quoted since it runs them with eval.
If the <then> or <else> clauses aren't commands, the function echos the proper value.
./script; ? Success! : "Failure :("
The function
?() {
local lastRet=$?
if [[ $1 == --help || $1 == -? ]]; then
echo $'\e[37;1mUsage:\e[0m
? [<condition>] <then> [:] <else>
If \e[37;1m<then>\e[0m and/or \e[37;1m<else>\e[0m are not valid commands, then their values are
printed to stdOut, otherwise they are executed. If \e[37;1m<condition>\e[0m is not
specified, evaluates the return code ($?) of the previous statement.
\e[37;1mExamples:\e[0m
myVar=$(? "[[ $x -eq 1 ]] foo bar)
\e[32;2m# myVar is set to "foo" if x is 1, else it is set to "bar"\e[0m
? "[[ $x = *foo* ]] "cat hello.txt" : "cat goodbye.txt"
\e[32;2m# runs cat on "hello.txt" if x contains the word "foo", else runs cat on
# "goodbye.txt"\e[0m
? "[[ $x -eq 1 ]] "./script1" "./script2"; ? "Succeeded!" "Failed :("
\e[32;2m# If x = 1, runs script1, else script2. If the run script succeeds, prints
# "Succeeded!", else prints "failed".\e[0m'
return
elif ! [[ $# -eq 2 || $# -eq 3 || $# -eq 4 && $3 == ':' ]]; then
1>&2 echo $'\e[37;1m?\e[0m requires 2 to 4 arguments
\e[37;1mUsage\e[0m: ? [<condition>] <then> [:] <else>
Run \e[37;1m? --help\e[0m for more details'
return 1
fi
local cmd
if [[ $# -eq 2 || $# -eq 3 && $2 == ':' ]]; then
cmd="[[ $lastRet -eq 0 ]]"
else
cmd="$1"
shift
fi
if [[ $2 == ':' ]]; then
eval "set -- '$1' '$3'"
fi
local result=$(eval "$cmd" && echo "$1" || echo "$2")
if command -v ${result[0]} &> /dev/null; then
eval "${result[#]}"
else
echo "${result[#]}"
fi
}
Obviously if you want the script to be shorter, you can remove the help text.
EDIT: I was unaware that ? acts as a placeholder character in a file name. Rather than matching any number of characters like *, it matches exactly one character. So, if you have a one-character file in your working directory, bash will try to run the filename as a command. I'm not sure how to get around this. I thought using command "?" ...args might work but, no dice.
Simplest ternary
brew list | grep -q bat && echo 'yes' || echo 'no'
This example will determine if you used homebrew to install bat or not yet
If true you will see "yes"
If false you will see "no"
I added the -q to suppress the grepped string output here, so you only see "yes" or "no"
Really the pattern you seek is this
doSomethingAndCheckTruth && echo 'yes' || echo 'no'
Tested with bash and zsh
Here are some options:
1- Use if then else in one line, it is possible.
if [[ "$2" == "raiz" ]] || [[ "$2" == '.' ]]; then pasta=''; else pasta="$2"; fi
2- Write a function like this:
# Once upon a time, there was an 'iif' function in MS VB ...
function iif(){
# Echoes $2 if 1,banana,true,etc and $3 if false,null,0,''
case $1 in ''|false|FALSE|null|NULL|0) echo $3;;*) echo $2;;esac
}
use inside script like this
result=`iif "$expr" 'yes' 'no'`
# or even interpolating:
result=`iif "$expr" "positive" "negative, because $1 is not true"`
3- Inspired in the case answer, a more flexible and one line use is:
case "$expr" in ''|false|FALSE|null|NULL|0) echo "no...$expr";;*) echo "yep $expr";;esac
# Expression can be something like:
expr=`expr "$var1" '>' "$var2"`
This is much like Vladimir's fine answer. If your "ternary" is a case of "if true, string, if false, empty", then you can simply do:
$ c="it was five"
$ b=3
$ a="$([[ $b -eq 5 ]] && echo "$c")"
$ echo $a
$ b=5
$ a="$([[ $b -eq 5 ]] && echo "$c")"
$ echo $a
it was five
A string-oriented alternative, that uses an array:
spec=(IGNORE REPLACE)
for p in {13..15}; do
echo "$p: ${spec[p==14]}";
done
which outputs:
13: IGNORE
14: REPLACE
15: IGNORE
to answer to : int a = (b == 5) ? c : d;
just write:
b=5
c=1
d=2
let a="(b==5)?c:d"
echo $a # 1
b=6;
c=1;
d=2;
let a="(b==5)?c:d"
echo $a # 2
remember that " expression " is equivalent to $(( expression ))
Two more answers
Here's some ways of thinking about this
bash integer variables
In addition to, dutCh, Vladimir and ghostdog74's corrects answers and because this question is regarding integer and tagged bash:
Is there a way to do something like this
int a = (b == 5) ? c : d;
There is a nice and proper way to work with integers under bash:
declare -i b=' RANDOM % 3 + 4 ' c=100 d=50 a=' b == 5 ? c : d '; echo $b '-->' $a
The output line from this command should by one of:
4 --> 50
5 --> 100
6 --> 50
Of course, declaring integer type of variable is to be done once:
declare -i a b c d
c=100 d=50 b=RANDOM%3+4
a=' b == 5 ? c : d '
echo $a $b
100 5
b=12 a=b==5?c:d
echo $a $b
50 12
Digression: Using a string as a math function:
mathString=' b == 5 ? c : d '
b=5 a=$mathString
echo $a $b
100 5
b=1 a=$mathString
echo $a $b
50 1
Based on parameter expansion and indirection
Following answers from Brad Parks and druid62, here is a way not limited to integer:
c=50 d=100 ar=([5]=c)
read -p 'Enter B: ' b
e=${ar[b]:-d};echo ${!e}
If b==5, then ar[b] is c and indirection do c is 50.
Else ar[any value other than 5] is empty, so parameter expansion will default to d, where indirection give 100.
Same demo using an array instead of an integer
ternArrayDemo(){
local -a c=(foo bar) d=(foo bar baz) e=(empty) ar=([5]=c [2]=d)
local b=${ar[$1]:-e}
b+=[#] # For array indirection
printf ' - %s\n' "${!b}"
}
Then
ternArrayDemo 0
- empty
ternArrayDemo 2
- foo
- bar
- baz
ternArrayDemo 4
- empty
ternArrayDemo 5
- foo
- bar
ternArrayDemo 6
- empty
Or using associative arrays
ternAssocArrayDemo(){
local -a c=(foo bar) d=(foo bar baz) e=(empty)
local -A ar=([foo]=c[#] [bar]=d[#] [baz]=d[-1])
local b=${ar[$1]:-e[#]}
printf ' - %s\n' "${!b}"
}
Then
ternAssocArrayDemo hello
- empty
ternAssocArrayDemo foo
- foo
- bar
ternAssocArrayDemo bar
- foo
- bar
- baz
ternAssocArrayDemo baz
- baz
The top answer [[ $b = 5 ]] && a="$c" || a="$d" should only be used if you are certain there will be no error after the &&, otherwise it will incorrectly excute the part after the ||.
To solve that problem I wrote a ternary function that behaves as it should and it even uses the ? and : operators:
Edit - new solution
Here is my new solution that does not use $IFS nor ev(a/i)l.
function executeCmds()
{
declare s s1 s2 i j k
declare -A cmdParts
declare pIFS=$IFS
IFS=$'\n'
declare results=($(echo "$1" | grep -oP '{ .*? }'))
IFS=$pIFS
s="$1"
for ((i=0; i < ${#results[#]}; i++)); do
s="${s/${results[$i]}/'\0'}"
results[$i]="${results[$i]:2:${#results[$i]}-3}"
results[$i]=$(echo ${results[$i]%%";"*})
done
s="$s&&"
let cmdParts[t]=0
while :; do
i=${cmdParts[t]}
let cmdParts[$i,t]=0
s1="${s%%"&&"*}||"
while :; do
j=${cmdParts[$i,t]}
let cmdParts[$i,$j,t]=0
s2="${s1%%"||"*};"
while :; do
cmdParts[$i,$j,${cmdParts[$i,$j,t]}]=$(echo ${s2%%";"*})
s2=${s2#*";"}
let cmdParts[$i,$j,t]++
[[ $s2 ]] && continue
break
done
s1=${s1#*"||"}
let cmdParts[$i,t]++
[[ $s1 ]] && continue
break
done
let cmdParts[t]++
s=${s#*"&&"}
[[ $s ]] && continue
break
done
declare lastError=0
declare skipNext=false
for ((i=0; i < ${cmdParts[t]}; i++ )) ; do
let j=0
while :; do
let k=0
while :; do
if $skipNext; then
skipNext=false
else
if [[ "${cmdParts[$i,$j,$k]}" == "\0" ]]; then
executeCmds "${results[0]}" && lastError=0 || lastError=1
results=("${results[#]:1}")
elif [[ "${cmdParts[$i,$j,$k]:0:1}" == "!" || "${cmdParts[$i,$j,$k]:0:1}" == "-" ]]; then
[ ${cmdParts[$i,$j,$k]} ] && lastError=0 || lastError=1
else
${cmdParts[$i,$j,$k]}
lastError=$?
fi
if (( k+1 < cmdParts[$i,$j,t] )); then
skipNext=false
elif (( j+1 < cmdParts[$i,t] )); then
(( lastError==0 )) && skipNext=true || skipNext=false
elif (( i+1 < cmdParts[t] )); then
(( lastError==0 )) && skipNext=false || skipNext=true
fi
fi
let k++
[[ $k<${cmdParts[$i,$j,t]} ]] || break
done
let j++
[[ $j<${cmdParts[$i,t]} ]] || break
done
done
return $lastError
}
function t()
{
declare commands="$#"
find="$(echo ?)"
replace='?'
commands="${commands/$find/$replace}"
readarray -d '?' -t statement <<< "$commands"
condition=${statement[0]}
readarray -d ':' -t statement <<< "${statement[1]}"
success="${statement[0]}"
failure="${statement[1]}"
executeCmds "$condition" || { executeCmds "$failure"; return; }
executeCmds "$success"
}
executeCmds separates each command individually, apart from the ones that should be skipped due to the && and || operators. It uses [] whenever a command starts with ! or a flag.
There are two ways to pass commands to it:
Pass the individual commands unquoted but be sure to quote ;, &&, and || operators.
t ls / ? ls qqq '||' echo aaa : echo bbb '&&' ls qq
Pass all the commands quoted:
t 'ls /a ? ls qqq || echo aaa : echo bbb && ls qq'
NB I found no way to pass in && and || operators as parameters unquoted, as they are illegal characters for function names and aliases, and I found no way to override bash operators.
Old solution - uses ev(a/i)l
function t()
{
pIFS=$IFS
IFS="?"
read condition success <<< "$#"
IFS=":"
read success failure <<< "$success"
IFS=$pIFS
eval "$condition" || { eval "$failure" ; return; }
eval "$success"
}
t ls / ? ls qqq '||' echo aaa : echo bbb '&&' ls qq
t 'ls /a ? ls qqq || echo aaa : echo bbb && ls qq'
What about such approach:
# any your function
function check () {
echo 'checking...';
# Change the following to 'true' to emulate a successful execution.
# Note: You can replace check function with any function you wish.
# Be aware in linux false and true are funcitons themselves. see 'help false' for instance.
false;
}
# double check pattern
check && echo 'update' \
|| check || echo 'create';
See how conditional statements works in the RxJs (i.e. filter pipe).
Yes, it is code duplication but more functional approach from my point of view.

Confused about bash arithmetic comparison

I've seen other answers about bash integer checks and comparisons, however the results I'm getting are very confusing to me.
Suppose I have this script:
if [[ $1 -eq $1 ]] ;then
echo "number"
else
echo "not number"
fi
if (( $1 >= 0 )) ;then
echo "number"
else
echo "not number"
fi
If I pass a string for parameter one , I get back "number".
That's because string is understood as a variable whose value is 0, and 0 >= 0 is true. Try with > (but it will report 0 as not number - but it already misclassifies all negative integers).
Cf:
a=1
b=a
x=b
(( x > 0 )) && echo 1
a=0
(( x > 0 )) || echo 0
or even
$ a=x
$ x=a
$ (( x > 0 ))
bash: ((: a: expression recursion level exceeded (error token is "a")
Bash tries hard to resolve the variable:
(
for l in a{1..1023} ; do
printf "$l\n$l="
done
echo 1
echo '((a1>0))'
) | tail -n+2 | bash

Bash programming: evaluation in conditional statements

In bash scripting the if condition statement is not working properly with using "&&"
ARGCOUNT=$#
if (( "$ARGCOUNT" != "2" )) ;then
echo "number of arguments must be two"
fi
DFLAG=$1
HFLAG=$2
if (((( $DFLAG = "Mon" )) || (( $DFLAG = "MON" )) || (( $DFLAG = "mon" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
echo " CS599 "
cd CS599
elif (((( $DFLAG = "Wed" )) || (( $DFLAG = "WED" )) || (( $DFLAG = "wed" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
cd CS699
echo " CS699 "
elif (((( $DFLAG = "Fri" )) || (( $DFLAG = "FRI" )) || (( $DFLAG = "fri" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
cd CS799
echo " CS799 "
else
echo "."
fi
my program is executing only else statement irrespective of arguments. means it evaluating if block false.
What is the problem ?
The parenthesis you use are for arithmetic evaluation. I think you are over using them, and it makes your script complicated.
This snippet below does work:
#!/bin/bash
ARGCOUNT=$#
if [ "$ARGCOUNT" -ne 2 ] ;then echo "number of arguments must be two"; fi
# put DFLAG in lower case (see man bash).
DFLAG=${1,,}
HFLAG=$2
if [ "$DFLAG" = 'mon' -a "$HFLAG" -ge 2 -a "$HFLAG" -le 4 ]; then
echo ok
else
echo failed
fi
As you can see, I optimized your expression:
Except for the case of $ARGCOUNT (which is safe because you initialized it to $#), don't forget to encase variable with double quote to avoid expansion.
In the declaration of DFLAG, I used the convert to lower case string operator (?). With that you won't have to check for each permutation of case in DFLAG. This might not work in bash3.
If you use the test or [ builtin, you can use -a between each expression to do a and.
Arithmetic evaluation with the test/[ builtin use the following operators: -ne (inequality), -eq (equality)-ge (greater or equal), -le (lesser or equals), -lt (lesser), -gt (greater).
As said in another answer, you can replace "$DFLAG" = 'mon' by "$DFLAG" == 'mon'. But this is not POSIX conformant (as said in my comment below) and I'm not enough knowledgeable on that to know if it's a good idea or not.
On a side note, if $HFLAG condition should always be the same, you can write your code like this:
if [ "$HFLAG" -ge 2 -a "$HFLAG" -le 4 ]; then
case "$DFLAG" in
mon|Mon|MON)
echo "monday";
;;
fry|Fry|FRY)
echo "friday";
;;
*)
echo "other"
;;
esac
fi
If that case, I putted back all permutation of case in case you were in bash3, to show you an example to do without ${DFLAG,}.
If you are looking for what mistake you did which is making condition to go to else part ...then it's simple mistake which almost every programmer do once in life ... using single "=" instead of "==" during comparison. Modify it accordingly and you should get expected flow/result in your script.
One eg.
$DFLAG = "Mon"
change to below notice the double equal sign
"$DFLAG" == "Mon"
First, you should go easy on the parenthesis. Those are fragile things.
Using Bash syntax (non-POSIX, less portable), you can write:
ARGCOUNT=$#
DFLAG=${1,,} # lower case
HFLAG=$2
# don't need the quotes in (( )) as we test arithmetic value
(( $ARGCOUNT != 2 )) && echo "number of arguments must be two"
shopt -s extglob # for #(1|2|3) below, see http://mywiki.wooledge.org/glob#extglob
if [[ $DFLAG = "mon" && $HFLAG == #(2|3|4) ]]; then
echo " CS599 "
cd CS599
if [[ $DFLAG = "wed" && $HFLAG == #(2|3|4) ]]; then
cd CS699
echo " CS699 "
if [[ $DFLAG = "fri" && $HFLAG == #(2|3|4) ]]; then
cd CS799
echo " CS799 "
else
echo "."
fi
Now you see how much you repeat yourself and can improve the algorithm. For instance, test HFLAG, if valid test DFLAG otherwise …
Read
Tests And Conditionals
Arithmetic Expression
Globbing extglob

Regarding Bash substring comparison

I try to test if a string starts with a certain prefix. But my script seems not work (I would expect the "if" branch will not get run). Can some Bash expert help to take a look? thanks!
Here is my code and test result:
$ cat testb.bash
#!/bin/bash
my_var="abcdefg";
if [[ "${my_var:0:5}"=="order" ]]; then
echo "value of my_var is ${my_var}.";
fi;
if [[ "${my_var:0:5}" -eq "order" ]]; then
echo "value of my_var is ${my_var}.";
fi;
if [ "${my_var:0:5}"="order" ]; then
echo "value of my_var is ${my_var}.";
fi;
$ bash -x testb.bash
+ my_var=abcdefg
+ [[ -n abcde==order ]]
+ echo 'value of my_var is abcdefg.'
value of my_var is abcdefg.
+ [[ abcde -eq order ]]
+ echo 'value of my_var is abcdefg.'
value of my_var is abcdefg.
+ '[' abcde=order ']'
+ echo 'value of my_var is abcdefg.'
value of my_var is abcdefg.
$
Whitespace is significant in this case. As you can see in the -x output, it understands the first condition as
[[ -n "${my_var:0:5}==order" ]]
Moreover, to test for a prefix, you can use a pattern:
[[ $my_var == order* ]]
To test the existence of substring, you can use either of these:
if [[ "$j" =~ string1 ]]; then
if [[ $j == *string1* ]]; then
In your particular case, you miss a space surounding ==, so instead of
if [[ "${my_var:0:5}"=="order" ]]; then
it should be
if [[ "${my_var:0:5}" == "order" ]]; then
^ ^
Finally, note that your condition was evaluated as true because it was evaluating if [ "string" ], which is true if string is not empty:
$ [ "a" ] && echo "yes"
yes
Test
$ cat a
#!/bin/bash
my_var="abcdefg";
if [[ "${my_var:0:5}" == "order" ]]; then
echo "value of my_var is ${my_var}."
elif [[ "${my_var:0:5}" == "abcde" ]]; then
echo "yeahaa"
else
echo "is not"
fi
$ ./a
yeahaa
Ok, i tested your code, you shoud such as the following code:
prefix="pre_order";
pre="pre_"
len=${#pre}
echo $len
if [[ "${prefix:0:len}" == "blahvlah" ]] ; then
echo "dddd"
fi;
Notes:
use == for string comparation
for ${} you should initilize a string variable before ${}
use len=${#pre} for lenght of string.
A POSIX-compliant way to test for a prefix is to attempt to remove the prefix, and compare the result to the original string. If the two are the same, the prefix is not present, the removal fails, and the expression expands to the original string.
prefix=foo
string=foobar
if [ "${string#$prefix}" = "$string" ]; then
printf "$string does not start with $prefix\n"
else
printf "$string starts with $prefix\n"
fi

Ternary operator (?:) in Bash

Is there a way to do something like this
int a = (b == 5) ? c : d;
using Bash?
ternary operator ? : is just short form of if/else
case "$b" in
5) a=$c ;;
*) a=$d ;;
esac
Or
[[ $b = 5 ]] && a="$c" || a="$d"
Code:
a=$([ "$b" == 5 ] && echo "$c" || echo "$d")
If the condition is merely checking if a variable is set, there's even a shorter form:
a=${VAR:-20}
will assign to a the value of VAR if VAR is set, otherwise it will assign it the default value 20 -- this can also be a result of an expression.
This approach is technically called "Parameter Expansion".
if [[ $b -eq 5 ]]; then a="$c"; else a="$d"; fi
The cond && op1 || op2 expression suggested in other answers has an inherent bug: if op1 has a nonzero exit status, op2 silently becomes the result; the error will also not be caught in -e mode. So, that expression is only safe to use if op1 can never fail (e.g., :, true if a builtin, or variable assignment without any operations that can fail (like division and OS calls)).
Note the "" quotes. They will prevent translation of all whitespace into single spaces.
Double square brackets as opposed to single ones prevent incorrect operation if $b is equal to a test operator (e.g. "-z"; a workaround with [ is [ "x$b" == "xyes" ] and it only works for string comparison); they also lift the requirement for quoting.
(( a = b==5 ? c : d )) # string + numeric
[ $b == 5 ] && { a=$c; true; } || a=$d
This will avoid executing the part after || by accident when the code between && and || fails.
We can use following three ways in Shell Scripting for ternary operator :
[ $numVar == numVal ] && resVar="Yop" || resVar="Nop"
Or
resVar=$([ $numVar == numVal ] && echo "Yop" || echo "Nop")
Or
(( numVar == numVal ? (resVar=1) : (resVar=0) ))
Update: Extending the answer for string computations with below ready-to-run example. This is making use of second format mentioned above.
$ strVar='abc';resVar=$([[ $strVar == 'abc' ]] && echo "Yop" || echo "Nop");echo $resVar
Yop
$ strVar='aaa';resVar=$([[ $strVar == 'abc' ]] && echo "Yop" || echo "Nop");echo $resVar
Nop
The let command supports most of the basic operators one would need:
let a=b==5?c:d;
Naturally, this works only for assigning variables; it cannot execute other commands.
Here is another option where you only have to specify the variable you're assigning once, and it doesn't matter whether what your assigning is a string or a number:
VARIABLE=`[ test ] && echo VALUE_A || echo VALUE_B`
Just a thought. :)
There's also a very similar but simpler syntax for ternary conditionals in bash:
a=$(( b == 5 ? 123 : 321 ))
The following seems to work for my use cases:
Examples
$ tern 1 YES NO
YES
$ tern 0 YES NO
NO
$ tern 52 YES NO
YES
$ tern 52 YES NO 52
NO
and can be used in a script like so:
RESULT=$(tern 1 YES NO)
echo "The result is $RESULT"
tern
#!/usr/bin/env bash
function show_help()
{
ME=$(basename "$0")
IT=$(cat <<EOF
Returns a ternary result
usage: BOOLEAN VALUE_IF_TRUE VALUE_IF_FALSE
e.g.
# YES
$ME 1 YES NO
# NO
$ME 0 YES NO
# NO
$ME "" YES NO
# YES
$ME "STRING THAT ISNT BLANK OR 0" YES NO
# INFO contains NO
INFO=\$($ME 0 YES NO)
EOF
)
echo "$IT"
echo
exit
}
if [ "$1" = "help" ] || [ "$1" = '?' ] || [ "$1" = "--help" ] || [ "$1" = "h" ]; then
show_help
fi
if [ -z "$3" ]
then
show_help
fi
# Set a default value for what is "false" -> 0
FALSE_VALUE=${4:-0}
function main
{
if [ "$1" == "$FALSE_VALUE" ] || [ "$1" = '' ]; then
echo $3
exit;
fi;
echo $2
}
main "$1" "$2" "$3"
Here's a general solution, that
works with string tests as well
feels rather like an expression
avoids any subtle side effects when the condition fails
Test with numerical comparison
a=$(if [ "$b" -eq 5 ]; then echo "$c"; else echo "$d"; fi)
Test with String comparison
a=$(if [ "$b" = "5" ]; then echo "$c"; else echo "$d"; fi)
(ping -c1 localhost&>/dev/null) && { echo "true"; } || { echo "false"; }
You can use this if you want similar syntax
a=$(( $((b==5)) ? c : d ))
Some people have already presented some nice alternatives. I wanted to get the syntax as close as possible, so I wrote a function named ?.
This allows for the syntax:
[[ $x -eq 1 ]]; ? ./script1 : ./script2
# or
? '[[ $x -eq 1 ]]' ./script1 : ./script2
In both cases, the : is optional. All arguments that have spaces, the values must be quoted since it runs them with eval.
If the <then> or <else> clauses aren't commands, the function echos the proper value.
./script; ? Success! : "Failure :("
The function
?() {
local lastRet=$?
if [[ $1 == --help || $1 == -? ]]; then
echo $'\e[37;1mUsage:\e[0m
? [<condition>] <then> [:] <else>
If \e[37;1m<then>\e[0m and/or \e[37;1m<else>\e[0m are not valid commands, then their values are
printed to stdOut, otherwise they are executed. If \e[37;1m<condition>\e[0m is not
specified, evaluates the return code ($?) of the previous statement.
\e[37;1mExamples:\e[0m
myVar=$(? "[[ $x -eq 1 ]] foo bar)
\e[32;2m# myVar is set to "foo" if x is 1, else it is set to "bar"\e[0m
? "[[ $x = *foo* ]] "cat hello.txt" : "cat goodbye.txt"
\e[32;2m# runs cat on "hello.txt" if x contains the word "foo", else runs cat on
# "goodbye.txt"\e[0m
? "[[ $x -eq 1 ]] "./script1" "./script2"; ? "Succeeded!" "Failed :("
\e[32;2m# If x = 1, runs script1, else script2. If the run script succeeds, prints
# "Succeeded!", else prints "failed".\e[0m'
return
elif ! [[ $# -eq 2 || $# -eq 3 || $# -eq 4 && $3 == ':' ]]; then
1>&2 echo $'\e[37;1m?\e[0m requires 2 to 4 arguments
\e[37;1mUsage\e[0m: ? [<condition>] <then> [:] <else>
Run \e[37;1m? --help\e[0m for more details'
return 1
fi
local cmd
if [[ $# -eq 2 || $# -eq 3 && $2 == ':' ]]; then
cmd="[[ $lastRet -eq 0 ]]"
else
cmd="$1"
shift
fi
if [[ $2 == ':' ]]; then
eval "set -- '$1' '$3'"
fi
local result=$(eval "$cmd" && echo "$1" || echo "$2")
if command -v ${result[0]} &> /dev/null; then
eval "${result[#]}"
else
echo "${result[#]}"
fi
}
Obviously if you want the script to be shorter, you can remove the help text.
EDIT: I was unaware that ? acts as a placeholder character in a file name. Rather than matching any number of characters like *, it matches exactly one character. So, if you have a one-character file in your working directory, bash will try to run the filename as a command. I'm not sure how to get around this. I thought using command "?" ...args might work but, no dice.
Simplest ternary
brew list | grep -q bat && echo 'yes' || echo 'no'
This example will determine if you used homebrew to install bat or not yet
If true you will see "yes"
If false you will see "no"
I added the -q to suppress the grepped string output here, so you only see "yes" or "no"
Really the pattern you seek is this
doSomethingAndCheckTruth && echo 'yes' || echo 'no'
Tested with bash and zsh
Here are some options:
1- Use if then else in one line, it is possible.
if [[ "$2" == "raiz" ]] || [[ "$2" == '.' ]]; then pasta=''; else pasta="$2"; fi
2- Write a function like this:
# Once upon a time, there was an 'iif' function in MS VB ...
function iif(){
# Echoes $2 if 1,banana,true,etc and $3 if false,null,0,''
case $1 in ''|false|FALSE|null|NULL|0) echo $3;;*) echo $2;;esac
}
use inside script like this
result=`iif "$expr" 'yes' 'no'`
# or even interpolating:
result=`iif "$expr" "positive" "negative, because $1 is not true"`
3- Inspired in the case answer, a more flexible and one line use is:
case "$expr" in ''|false|FALSE|null|NULL|0) echo "no...$expr";;*) echo "yep $expr";;esac
# Expression can be something like:
expr=`expr "$var1" '>' "$var2"`
This is much like Vladimir's fine answer. If your "ternary" is a case of "if true, string, if false, empty", then you can simply do:
$ c="it was five"
$ b=3
$ a="$([[ $b -eq 5 ]] && echo "$c")"
$ echo $a
$ b=5
$ a="$([[ $b -eq 5 ]] && echo "$c")"
$ echo $a
it was five
A string-oriented alternative, that uses an array:
spec=(IGNORE REPLACE)
for p in {13..15}; do
echo "$p: ${spec[p==14]}";
done
which outputs:
13: IGNORE
14: REPLACE
15: IGNORE
to answer to : int a = (b == 5) ? c : d;
just write:
b=5
c=1
d=2
let a="(b==5)?c:d"
echo $a # 1
b=6;
c=1;
d=2;
let a="(b==5)?c:d"
echo $a # 2
remember that " expression " is equivalent to $(( expression ))
Two more answers
Here's some ways of thinking about this
bash integer variables
In addition to, dutCh, Vladimir and ghostdog74's corrects answers and because this question is regarding integer and tagged bash:
Is there a way to do something like this
int a = (b == 5) ? c : d;
There is a nice and proper way to work with integers under bash:
declare -i b=' RANDOM % 3 + 4 ' c=100 d=50 a=' b == 5 ? c : d '; echo $b '-->' $a
The output line from this command should by one of:
4 --> 50
5 --> 100
6 --> 50
Of course, declaring integer type of variable is to be done once:
declare -i a b c d
c=100 d=50 b=RANDOM%3+4
a=' b == 5 ? c : d '
echo $a $b
100 5
b=12 a=b==5?c:d
echo $a $b
50 12
Digression: Using a string as a math function:
mathString=' b == 5 ? c : d '
b=5 a=$mathString
echo $a $b
100 5
b=1 a=$mathString
echo $a $b
50 1
Based on parameter expansion and indirection
Following answers from Brad Parks and druid62, here is a way not limited to integer:
c=50 d=100 ar=([5]=c)
read -p 'Enter B: ' b
e=${ar[b]:-d};echo ${!e}
If b==5, then ar[b] is c and indirection do c is 50.
Else ar[any value other than 5] is empty, so parameter expansion will default to d, where indirection give 100.
Same demo using an array instead of an integer
ternArrayDemo(){
local -a c=(foo bar) d=(foo bar baz) e=(empty) ar=([5]=c [2]=d)
local b=${ar[$1]:-e}
b+=[#] # For array indirection
printf ' - %s\n' "${!b}"
}
Then
ternArrayDemo 0
- empty
ternArrayDemo 2
- foo
- bar
- baz
ternArrayDemo 4
- empty
ternArrayDemo 5
- foo
- bar
ternArrayDemo 6
- empty
Or using associative arrays
ternAssocArrayDemo(){
local -a c=(foo bar) d=(foo bar baz) e=(empty)
local -A ar=([foo]=c[#] [bar]=d[#] [baz]=d[-1])
local b=${ar[$1]:-e[#]}
printf ' - %s\n' "${!b}"
}
Then
ternAssocArrayDemo hello
- empty
ternAssocArrayDemo foo
- foo
- bar
ternAssocArrayDemo bar
- foo
- bar
- baz
ternAssocArrayDemo baz
- baz
The top answer [[ $b = 5 ]] && a="$c" || a="$d" should only be used if you are certain there will be no error after the &&, otherwise it will incorrectly excute the part after the ||.
To solve that problem I wrote a ternary function that behaves as it should and it even uses the ? and : operators:
Edit - new solution
Here is my new solution that does not use $IFS nor ev(a/i)l.
function executeCmds()
{
declare s s1 s2 i j k
declare -A cmdParts
declare pIFS=$IFS
IFS=$'\n'
declare results=($(echo "$1" | grep -oP '{ .*? }'))
IFS=$pIFS
s="$1"
for ((i=0; i < ${#results[#]}; i++)); do
s="${s/${results[$i]}/'\0'}"
results[$i]="${results[$i]:2:${#results[$i]}-3}"
results[$i]=$(echo ${results[$i]%%";"*})
done
s="$s&&"
let cmdParts[t]=0
while :; do
i=${cmdParts[t]}
let cmdParts[$i,t]=0
s1="${s%%"&&"*}||"
while :; do
j=${cmdParts[$i,t]}
let cmdParts[$i,$j,t]=0
s2="${s1%%"||"*};"
while :; do
cmdParts[$i,$j,${cmdParts[$i,$j,t]}]=$(echo ${s2%%";"*})
s2=${s2#*";"}
let cmdParts[$i,$j,t]++
[[ $s2 ]] && continue
break
done
s1=${s1#*"||"}
let cmdParts[$i,t]++
[[ $s1 ]] && continue
break
done
let cmdParts[t]++
s=${s#*"&&"}
[[ $s ]] && continue
break
done
declare lastError=0
declare skipNext=false
for ((i=0; i < ${cmdParts[t]}; i++ )) ; do
let j=0
while :; do
let k=0
while :; do
if $skipNext; then
skipNext=false
else
if [[ "${cmdParts[$i,$j,$k]}" == "\0" ]]; then
executeCmds "${results[0]}" && lastError=0 || lastError=1
results=("${results[#]:1}")
elif [[ "${cmdParts[$i,$j,$k]:0:1}" == "!" || "${cmdParts[$i,$j,$k]:0:1}" == "-" ]]; then
[ ${cmdParts[$i,$j,$k]} ] && lastError=0 || lastError=1
else
${cmdParts[$i,$j,$k]}
lastError=$?
fi
if (( k+1 < cmdParts[$i,$j,t] )); then
skipNext=false
elif (( j+1 < cmdParts[$i,t] )); then
(( lastError==0 )) && skipNext=true || skipNext=false
elif (( i+1 < cmdParts[t] )); then
(( lastError==0 )) && skipNext=false || skipNext=true
fi
fi
let k++
[[ $k<${cmdParts[$i,$j,t]} ]] || break
done
let j++
[[ $j<${cmdParts[$i,t]} ]] || break
done
done
return $lastError
}
function t()
{
declare commands="$#"
find="$(echo ?)"
replace='?'
commands="${commands/$find/$replace}"
readarray -d '?' -t statement <<< "$commands"
condition=${statement[0]}
readarray -d ':' -t statement <<< "${statement[1]}"
success="${statement[0]}"
failure="${statement[1]}"
executeCmds "$condition" || { executeCmds "$failure"; return; }
executeCmds "$success"
}
executeCmds separates each command individually, apart from the ones that should be skipped due to the && and || operators. It uses [] whenever a command starts with ! or a flag.
There are two ways to pass commands to it:
Pass the individual commands unquoted but be sure to quote ;, &&, and || operators.
t ls / ? ls qqq '||' echo aaa : echo bbb '&&' ls qq
Pass all the commands quoted:
t 'ls /a ? ls qqq || echo aaa : echo bbb && ls qq'
NB I found no way to pass in && and || operators as parameters unquoted, as they are illegal characters for function names and aliases, and I found no way to override bash operators.
Old solution - uses ev(a/i)l
function t()
{
pIFS=$IFS
IFS="?"
read condition success <<< "$#"
IFS=":"
read success failure <<< "$success"
IFS=$pIFS
eval "$condition" || { eval "$failure" ; return; }
eval "$success"
}
t ls / ? ls qqq '||' echo aaa : echo bbb '&&' ls qq
t 'ls /a ? ls qqq || echo aaa : echo bbb && ls qq'
What about such approach:
# any your function
function check () {
echo 'checking...';
# Change the following to 'true' to emulate a successful execution.
# Note: You can replace check function with any function you wish.
# Be aware in linux false and true are funcitons themselves. see 'help false' for instance.
false;
}
# double check pattern
check && echo 'update' \
|| check || echo 'create';
See how conditional statements works in the RxJs (i.e. filter pipe).
Yes, it is code duplication but more functional approach from my point of view.

Resources