Is there a way to do something like this
int a = (b == 5) ? c : d;
using Bash?
The ternary operator ? : is just a short form of if/then/else:
case "$b" in
5) a=$c ;;
*) a=$d ;;
esac
Or:
[[ $b = 5 ]] && a="$c" || a="$d"
= operator tests for string equality, not numeric equality (i.e. [[ 05 = 5 ]] is false). If you want numeric comparison, use -eq instead.if/then/elsecond && op1 || op2 construct has an inherent bug: if op1 has nonzero exit status for whatever reason, the result will silently become op2. if cond; then op1; else op2; fi is one line too and doesn't have that defect.&& it is imperative that you understand the meaning of AND". The meaning is "both evaluate to true". If opt1 can be falsey, you should inverse your cond if and only if opt2 cannot be falsey. If that is not true, your logic is not a candidate for && … || shorthand/logic and you must use if … then … else longhand/logic.Code:
a=$([ "$b" == 5 ] && echo "$c" || echo "$d")
echo "$c" is an aliased, multi-lined command (like echo 1; echo 2), you should enclose it in parentheses.&& won't have a non-zero exit status. Otherwise, a && b || c will "unexpectedly" run c if a succeeds but b fails.echo and doesn't quote the result. To be honest this approach is shoehorning the concept of a ternary operator into a language that doesn't have one. If I came across this in the wild I would be bewilderedIf 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".
a=${1:-'my-hyphenated-text'}:-)${VAR:-yes:-no}.:+. You can do ${${VAR:+yes}:-no}... if VAR is set & non-null, this will expand to yes and if VAR is unset or null it expands to no.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
(( )) treats any/all strings as 0a=$(( b==5 ? c : d ))[ $b == 5 ] && { a=$c; true; } || a=$d
This will avoid executing the part after || by accident when the code between && and || fails.
-e mode: (set -o errexit; [ 5 == 5 ] && { false; true; echo "success"; } || echo "failure"; echo $?; echo "further on";) -> success 0 further on: bulit-in instead of true to save exec-ing an external program.&& .. || and still catch a failed command in between them?&& and || by definition apply to the entire command before them. So, if you have two commands before it (regardless of how they are combined), you cannot apply it to only to one of them and not the other. You can emulate if/then/else logic with flag variables, but why bother if there's if/then/else proper?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. :)
[ test ]. So the construct is only safe to use if you "know" that the command doesn't output anything to stdout.VARIABLE="`[ test ] && echo \"VALUE_A\" || echo \"VALUE_B\"`" .There's also a very similar but simpler syntax for ternary conditionals in bash:
a=$(( b == 5 ? 123 : 321 ))
test results.The following seems to work for my use cases:
$ 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"
#!/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"
tern part of Bash? Mac doesn't seem to have itchmod 700 tern in the same folder. Now you'll have a tern command in your terminalHere's a general solution, that
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)
[ instead of [[ would cause an error if $b is equal to a test operator (e.g. "-z"). 3) It doesn't quote the $() result which would cause all whitespace to become single spaces.(ping -c1 localhost&>/dev/null) && { echo "true"; } || { echo "false"; }
echo "false" may occur when ping is successful, but where, for some reason, however unlikely, the echo "true" part returns a non-zero exit status (see ShellCheck SC2015). To echo "false" only when ping fails, regardless of echo "true"’s exit status, move the first grouping brace { to the head: { (ping -c1 localhost&>/dev/null) && echo "true"; } || { echo "false"; }.You can use this if you want similar syntax
a=$(( $((b==5)) ? c : d ))
a=$(((b==5) ? : c : d)) - $((...)) is needed only when we want to assign the result of the arithmetic to another variable.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 :("
?() {
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.
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"
doSomethingAndCheckTruth && echo 'yes' || echo 'no'
Tested with bash and zsh
&& fails, then the || will be run, which is not quite the desired behavior.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"`
Here's some ways of thinking about this
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
Note: This syntax is recommanded. Declaring property of a variable, then assigning a value is better to be done separately.
mathString=' b == 5 ? c : d '
b=5 a=$mathString
echo $a $b
100 5
b=1 a=$mathString
echo $a $b
50 1
Note: While $mathString do contain spaces, double-quotes are not needed in a=$mathString assignment.
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}
b==5, then ar[b] is c and indirection do c is 50.ar[any value other than 5] is empty, so parameter expansion will default to d, where indirection give 100.ternArrayDemo(){
local -a c d e ar
local b
c=(foo bar) d=(foo bar baz) e=(empty)
ar=([5]=c [2]=d)
b=${ar[$1]:-e}
b+=[@] # For array indirection
printf ' - %s\n' "${!b}"
}
$ar only 2 and 5 are pointing to some variable name.e do contain default value.Then
ternArrayDemo 0
- empty
ternArrayDemo 2
- foo
- bar
- baz
ternArrayDemo 4
- empty
ternArrayDemo 5
- foo
- bar
ternArrayDemo 6
- empty
ternAssocArrayDemo(){
local -a c d e
local -A ar
local b
c=( foo bar ) d=( foo bar baz ) e=( empty )
ar=( [foo]=c[@] [bar]=d[@] [baz]=d[*] )
b=${ar["$1"]:-e[@]}
printf ' - %s\n' "${!b}"
}
Then
ternAssocArrayDemo hello
- empty
ternAssocArrayDemo foo
- foo
- bar
ternAssocArrayDemo bar
- foo
- bar
- baz
ternAssocArrayDemo baz
- foo bar baz
ternAssocArrayDemo() {
local -a _var_c=( Hello\ world. Good\ day! ) _var_d=( foo bar baz ) _var_e=(
"${_var_d[@]:1}") _var_eMsg=( empty slot )
local -A "_assocArr=( [foo]=c[@] [bar]=d[@] [baz]=d[*] [fuu]=e[@] )"
local _bind_var=_var_${_assocArr["$1"]:-eMsg[*]}
printf ' - %s\n' "${!_bind_var}"
}
Then
for arg in foo bar baz fuu faa; do
echo $arg
ternAssocArrayDemo $arg
done
foo
- Hello world.
- Good day!
bar
- foo
- bar
- baz
baz
- foo bar baz
fuu
- bar
- baz
faa
- empty slot
declare -i b=' RANDOM % 3 + 4 ': TIL that in bash, when a variable is declared as an integer, then the value assigned to it is evaluated as an arithmetic expression.RANDOM? Is that a mathematical operation or your name for an arbitrary variable?RANDOM is an integer bash pseudo variable that will produce random integer between 0 and 32767, each time they are accessed. You could try it in any terminal console: echo $RANDOM, or declare -i myVar=RANDOM;echo $myVarThis 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
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.
echo 'update' -- so you don't really solve the a && b || c bug with this.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:
;, &&, and || operators.t ls / ? ls qqq '||' echo aaa : echo bbb '&&' ls qq
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'
bashdoes have something similar to the "ternary operator" however inbashthis is called the "conditional operator"expr?expr:expr(seeman bashgoto section "Arithmetic Evaluation"). Keep in mind thebash"conditional operator" is tricky and has some gotchas.((...)). See Shell Arithmetic.$(( ))and arithmethic evaluation(( )). See alsohttps://mywiki.wooledge.org/ArithmeticExpression.