725

Is there a way to do something like this

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

using Bash?

codeforester
  • 39,467
  • 16
  • 112
  • 140
En_t8
  • 7,595
  • 5
  • 20
  • 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
  • 2
    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

23 Answers23

739

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"
Sławomir Lenart
  • 7,543
  • 4
  • 45
  • 61
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • 135
    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
  • 14
    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
  • 7
    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
  • 153
    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
  • 3
    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
  • 1
    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
  • You could consider unsiing [bash integer variable](https://stackoverflow.com/a/73539561/1765658)! Your answer is cited. – F. Hauri - Give Up GitHub Aug 30 '22 at 08:49
  • `?` is `&&`, `:` is `||` – Timo Dec 06 '22 at 12:07
586

Code:

a=$([ "$b" == 5 ] && echo "$c" || echo "$d")
Vladimir
  • 9,913
  • 4
  • 26
  • 37
  • 81
    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
  • 23
    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
  • You could consider unsiing [bash integer variable](https://stackoverflow.com/a/73539561/1765658)! Your answer is cited. – F. Hauri - Give Up GitHub Aug 30 '22 at 08:49
  • several issues with this, starts a subshell, uses `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 bewildered – CervEd Jan 24 '23 at 10:36
254

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
  • 28,235
  • 9
  • 60
  • 81
JWL
  • 13,591
  • 7
  • 57
  • 63
  • 10
    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
  • 5
    [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
  • 22
    @JustinWrobel - unfortunately no syntax like `${VAR:-yes:-no}`. – Ken Williams Dec 06 '18 at 21:40
  • 7
    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
  • 7
    @KenWilliams Actually there is a syntax like that, it uses a second type of parameter expansion, `:+`. 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`. – Joshua Skrzypek Oct 19 '21 at 17:28
  • 2
    @JoshuaSkrzypek, no, this will not work. `VAR=1; echo "${${VAR:+yes}:-no}"` → `-bash: ${${VAR:+yes}:-no}: bad substitution` and `VAR=; echo "${${VAR:+yes}:-no}"` → `-bash: ${${VAR:+yes}:-no}: bad substitution` – Julian Mehnle Nov 12 '21 at 00:58
  • 3
    `${${VAR+yes}:-no}` works for `zsh (5.8)` – Phu Ngo Dec 27 '21 at 06:55
  • 2
    @JulianMehnle You're totally right, that's a zshism, not a bashism – Joshua Skrzypek Aug 26 '22 at 17:16
133
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.

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
  • Great point! I would just like to add that this is exactly the kind of sneaky bug that [ShellCheck](https://www.shellcheck.net/) finds for you. In this case, the relevant [wiki page](https://www.shellcheck.net/wiki/SC2015). – etauger Jul 07 '23 at 18:22
60
(( a = b==5 ? c : d )) # string + numeric
dutCh
  • 617
  • 4
  • 2
42
[ $b == 5 ] && { a=$c; true; } || a=$d

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

devnull
  • 118,548
  • 33
  • 236
  • 227
Sir Athos
  • 9,403
  • 2
  • 22
  • 23
  • 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
  • 3
    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
32

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
Pavan Kumar
  • 4,182
  • 1
  • 30
  • 45
Sujay U N
  • 4,974
  • 11
  • 52
  • 88
  • 2
    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
  • 1
    Each of the suggested ways has limitations/caveats which are not explained. So using this answer is dangerous. – ivan_pozdeev Aug 31 '21 at 10:07
14

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
  • 621
  • 5
  • 13
  • 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
14

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,597
  • 16
  • 20
12

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

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

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"
Brad Parks
  • 66,836
  • 64
  • 257
  • 336
8

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
  • 28,235
  • 9
  • 60
  • 81
Stefan Haberl
  • 9,812
  • 7
  • 72
  • 81
5
(ping -c1 localhost&>/dev/null) && { echo "true"; } || {  echo "false"; }
wibble
  • 635
  • 1
  • 5
  • 16
  • This solution has the same defect [ivan_pozdeev describes](https://stackoverflow.com/q/3953645#comment73915813_3953666) above where for this solution, `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](https://github.com/koalaman/shellcheck/wiki/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"; }`. – Lucas May 01 '22 at 18:22
4

You can use this if you want similar syntax

a=$(( $((b==5)) ? c : d ))
  • 1
    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
4

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.

dx_over_dt
  • 13,240
  • 17
  • 54
  • 102
3

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
  • 12,047
  • 89
  • 66
  • See this [comment](https://stackoverflow.com/questions/3953645/ternary-operator-in-bash#comment73915813_3953666). If the `&&` fails, then the `||` will be run, which is not quite the desired behavior. – dx_over_dt Jun 18 '21 at 16:53
  • My answer here also has some ternary examples: https://stackoverflow.com/a/62527825/1783588 – jasonleonhard Mar 21 '22 at 21:33
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,686
  • 25
  • 20
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
  • 66,273
  • 12
  • 162
  • 149
1

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
  • 109
  • 3
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,560
  • 12
  • 26
1

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 :

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
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
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
  • 7,242
  • 3
  • 52
  • 70
0

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.

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286