521

Is there a way to do something like this

int a = (b == 5) ? c : d;

using Bash?

codeforester
  • 28,846
  • 11
  • 78
  • 104
En_t8
  • 5,535
  • 4
  • 16
  • 14
  • 1
    [@dutCh's answer](https://stackoverflow.com/a/3955920/52074) shows that `bash` does have something similar to the "ternary operator" however in `bash` this is called the "conditional operator" `expr?expr:expr` (see `man bash` goto section "Arithmetic Evaluation"). Keep in mind the `bash` "conditional operator" is tricky and has some gotchas. – Trevor Boyd Smith Jul 20 '17 at 13:36
  • Bash does have a ternary operator for integers and it works inside the arithmetic expression `((...))`. See [Shell Arithmetic](https://www.gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html#Shell-Arithmetic). – codeforester Sep 01 '18 at 19:14
  • 1
    Just as @codeforester mentioned, ternary operator works with arithmetic expansion `$(( ))` and arithmethic evaluation `(( ))`. See also `https://mywiki.wooledge.org/ArithmeticExpression`. – Kai Apr 13 '20 at 05:10

20 Answers20

562

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"
Xiong Chiamiov
  • 11,582
  • 8
  • 55
  • 95
ghostdog74
  • 286,686
  • 52
  • 238
  • 332
  • 114
    Note that the `=` operator tests for string equality, not numeric equality (i.e. `[[ 05 = 5 ]]` is false). If you want numeric comparison, use `-eq` instead. – Gordon Davisson Oct 17 '10 at 19:54
  • 11
    It's more of a short form for `if/then/else` – vol7ron Apr 04 '13 at 20:10
  • 17
    It's a genius way to utilize the short-circuit behavior to get a ternary operator effect :) :) :) – mtk May 10 '13 at 06:16
  • @XiongChiamiov, your double bracket expression is incorrect. The order of the last 2 assignment operands (`c` and `d`) must be reversed to be equivalent to the `?` operator in the OP's question: `[[ $b = 5 ]] && a="$d" || a="$c"`. – hobs Jul 03 '14 at 21:15
  • 6
    why the [[ and ]] ? it works just as well like this : [ $b = 5 ] && a="$c" || a="$d" – kdubs Oct 29 '14 at 14:57
  • 1
    in case it helps anyone, I used the following based off the answer from @ghostdog74 & modified to use an arithmetic context: `(( 3 > 1 )) && echo "yup" || echo "nooope")`, and thus `answer=$( (( 3 > 1 )) && echo "ouiii" || echo "nooope")` or the shorter `(( 3 > 7 )) && answer="yup" || answer="nop"` – StephaneAG Jul 12 '15 at 19:08
  • 1
    @StephaneAG You have one closing bracket too much at `(3 > 1))`. The last one should be left off. – kaiser Feb 26 '16 at 18:11
  • kdubs the documentation recommends to use [[ ]] when doing direct number comparisons. [ ] works better with strings. – Sergio Abreu Jan 03 '17 at 10:36
  • 100
    The `cond && 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`](http://stackoverflow.com/a/25119904/648265) is one line too and doesn't have that defect. – ivan_pozdeev Apr 15 '17 at 17:19
  • If you aren't using the exit status of `op1`, you can use `cond && { op1 ||: } || op2`. `:` is equivalent to `true` (https://stackoverflow.com/questions/3224878/what-is-the-purpose-of-the-colon-gnu-bash-builtin -- the `:` is required in POSIX shells, apparently more portable than `true`, but read for more info.) – John P Feb 23 '18 at 03:12
  • 2
    I appreciate the warning from @ivan_pozdeev, but I object to calling it a "bug". I would rather state "when using `&&` 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. – Bruno Bronosky Aug 02 '19 at 04:33
  • How to demonstrate @BrunoBronosky's suggestion: `[[ "foo" == "foo" ]] && echo yes || echo no` behaves as you would expect. Now try `[[ "foo" == "foo" ]] && false || echo no` to simulate op1 failing. oops! – fbicknel Jan 08 '20 at 14:24
  • @GordonDavisson for numeric comparison use ((b == 5)) you can omit the $ inside `((...))` https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Arithmetic – Roland Apr 30 '20 at 10:55
442

Code:

a=$([ "$b" == 5 ] && echo "$c" || echo "$d")
Vladimir
  • 8,223
  • 4
  • 24
  • 36
  • 66
    this is better than the others... the point about the tertiary operator is that it's an operator, hence it's proper context is in an expression, hence it must return a value. – nic ferrier Mar 16 '12 at 07:56
  • 2
    This is the most concise way. Be aware that if the part with `echo "$c"` is an aliased, multi-lined command (like `echo 1; echo 2`), you should enclose it in parentheses. – Matt Sep 30 '13 at 18:04
  • 2
    This will also capture any output of the tested command, too (yes, in this particular case, we "know" it doesn't produce any). – ivan_pozdeev Jan 23 '18 at 20:16
  • 13
    This *chain* of operators only behaves like a ternary operator if you are positive that the command after `&&` won't have a non-zero exit status. Otherwise, `a && b || c` will "unexpectedly" run `c` if `a` succeeds but `b` fails. – chepner May 15 '18 at 17:57
188

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".

wjandrea
  • 16,334
  • 5
  • 30
  • 53
nemesisfixx
  • 11,399
  • 7
  • 53
  • 59
  • 7
    In the case of passing a string parameter with hyphens in it, I had to use quote marks: `a=${1:-'my-hyphenated-text'}` – saranicole Apr 26 '16 at 16:06
  • 3
    [link for the lazy](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html) - there are additional operators than just substitute (`:-`) – Justin Wrobel Oct 09 '18 at 14:07
  • 15
    @JustinWrobel - unfortunately no syntax like `${VAR:-yes:-no}`. – Ken Williams Dec 06 '18 at 21:40
  • Not what the OP wanted, but just wanted to say thanks for teaching me something new today that fit my scenario perfectly ^_^ – stevenhaddox May 08 '21 at 00:58
86
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. 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.

cxw
  • 15,429
  • 2
  • 37
  • 69
ivan_pozdeev
  • 28,628
  • 13
  • 85
  • 130
  • 1
    I've also seen prefixing, I believe `[ "x$b" -eq "x" ]` used to test for the empty string specifically. I like to use the syntaxes used by zsh ('PARAMETER EXPANSION' in the zshexpn manpage) but I don't know much about their portability. – John P Feb 23 '18 at 03:31
  • 1
    @JohnP https://stackoverflow.com/questions/174119/why-do-shell-script-comparisons-often-use-xvar-xyes – ivan_pozdeev Feb 23 '18 at 08:50
50
(( a = b==5 ? c : d )) # string + numeric
dutCh
  • 517
  • 3
  • 2
  • 36
    This is good for **numeric** comparisons and assignments, but it will give unpredictable results if you use it for string comparisons and assignments.... `(( ))` treats any/all strings as `0` – Peter.O May 12 '11 at 22:51
  • 3
    This can also be written: `a=$(( b==5 ? c : d ))` – joeytwiddle Dec 30 '18 at 09:52
37
[ $b == 5 ] && { a=$c; true; } || a=$d

This will avoid executing the part after || by accident when the code between && and || fails.

devnull
  • 103,635
  • 29
  • 207
  • 208
Sir Athos
  • 7,586
  • 2
  • 20
  • 21
  • This will still not catch the error in `-e` mode: `(set -o errexit; [ 5 == 5 ] && { false; true; echo "success"; } || echo "failure"; echo $?; echo "further on";)` -> `success 0 further on` – ivan_pozdeev Apr 01 '16 at 14:14
  • 2
    Use the `:` bulit-in instead of `true` to save `exec`-ing an external program. – Tom Hale Jan 13 '17 at 04:11
  • @ivan_pozdeev Is there any way to use `&&` .. `||` and still catch a failed command in between them? – Tom Hale Jan 13 '17 at 04:32
  • 2
    @TomHale No. `&&` 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? – ivan_pozdeev Jan 13 '17 at 05:35
15

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. :)

Jasonovich
  • 577
  • 4
  • 10
  • 2
    A major downside: it will also capture the stdout of `[ test ]`. So the construct is only safe to use if you "know" that the command doesn't output anything to stdout. – ivan_pozdeev Jan 09 '18 at 11:43
  • 1
    Also the same as https://stackoverflow.com/questions/3953645/ternary-operator-in-bash/3953712#3953712 plus the need to quote and escape quotes inside. The space-tolerant version will look like `VARIABLE="\`[ test ] && echo \"VALUE_A\" || echo \"VALUE_B\"\`"` . – ivan_pozdeev Jan 23 '18 at 20:33
13

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.

emu
  • 1,276
  • 13
  • 19
12

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) ))
Sujay U N
  • 3,942
  • 6
  • 39
  • 65
  • 1
    This is actually (the first and second specifically, of these three examples) the most appropriate answer to this question IMHO. – netpoetica Oct 10 '18 at 11:52
10

There's also a very similar but simpler syntax for ternary conditionals in bash:

a=$(( b == 5 ? 123 : 321  ))
Andre Dias
  • 109
  • 1
  • 4
8

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

function show_help()
{
  echo ""
  echo "usage: BOOLEAN VALUE_IF_TRUE VALUE_IF_FALSE {FALSE_VALUE}"
  echo ""
  echo "e.g. "
  echo ""
  echo "tern 1 YES NO                            => YES"
  echo "tern 0 YES NO                            => NO"
  echo "tern "" YES NO                           => NO"
  echo "tern "ANY STRING THAT ISNT 1" YES NO     => NO"
  echo "ME=$(tern 0 YES NO)                      => ME contains NO"
  echo ""

  exit
}

if [ "$1" == "help" ]
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" ]; then
    echo $3
    exit;
  fi;

  echo $2
}

main "$1" "$2" "$3"
Community
  • 1
  • 1
Brad Parks
  • 54,283
  • 54
  • 221
  • 287
6

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)
wjandrea
  • 16,334
  • 5
  • 30
  • 53
Stefan Haberl
  • 7,787
  • 6
  • 59
  • 64
4
(ping -c1 localhost&>/dev/null) && { echo "true"; } || {  echo "false"; }
wibble
  • 565
  • 1
  • 4
  • 15
4

You can use this if you want similar syntax

a=$(( $((b==5)) ? c : d ))
  • This works only with integers. You can write it more simply as `a=$(((b==5) ? : c : d))` - `$((...))` is needed only when we want to assign the result of the arithmetic to another variable. – codeforester Sep 01 '18 at 19:09
2

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"`
Sergio Abreu
  • 2,113
  • 21
  • 14
2

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

jasonleonhard
  • 6,357
  • 53
  • 49
1

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
Bruno Bronosky
  • 54,357
  • 9
  • 132
  • 120
1

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

Sapphire_Brick
  • 1,159
  • 7
  • 22
0

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
druid62
  • 87
  • 3
0

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:

  1. Pass the individual commands unquoted but be sure to quote ;, &&, and || operators.
t ls / ? ls qqq '||' echo aaa : echo bbb '&&' ls qq
  1. 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'
Dan Bray
  • 5,616
  • 3
  • 43
  • 51