Is there a way to do something like this
int a = (b == 5) ? c : d;using Bash?
321 Answers
ternary operator ? : is just short form of if/else
case "$b" in 5) a=$c ;; *) a=$d ;;
esacOr
[[ $b = 5 ]] && a="$c" || a="$d" 13 Code:
a=$([ "$b" == 5 ] && echo "$c" || echo "$d") 4 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".
7if [ "$b" -eq 5 ]; then a="$c"; else a="$d"; fiThe 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. The first pair will prevent a syntax error if $b is blank or has whitespace. Others will prevent translation of all whitespace into single spaces.
(( a = b==5 ? c : d )) # string + numeric 2 [ $b == 5 ] && { a=$c; true; } || a=$dThis will avoid executing the part after || by accident when the code between && and || fails.
4We 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 2 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. :)
2The 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.
1There's also a very similar but simpler syntax for ternary conditionals in bash:
a=$(( b == 5 ? 123 : 321 )) 2 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
NOand 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" 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"; } 1 You can use this if you want similar syntax
a=$(( $((b==5)) ? c : d )) 1 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
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 : ./script2In 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.
Here are some options:
1- Use if then else in one line, it is possible.
if [[ "$2" == "raiz" ]] || [[ "$2" == '.' ]]; then pasta=''; else pasta="$2"; fi2- 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 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 # 2remember that " expression " is equivalent to $(( expression ))
A string-oriented alternative, that uses an array:
spec=(IGNORE REPLACE)
for p in {13..15}; do echo "$p: ${spec[p==14]}";
donewhich outputs:
13: IGNORE
14: REPLACE
15: IGNORE 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'