Ternary operator (?:) in Bash

BashSyntaxConditional Operator

Bash Problem Overview


Is there a way to do something like this

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

using Bash?

Bash Solutions


Solution 1 - 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"

Solution 2 - Bash

Code:

a=$([ "$b" == 5 ] && echo "$c" || echo "$d")

Solution 3 - Bash

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

Solution 4 - Bash

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.

Solution 5 - Bash

(( a = b==5 ? c : d )) # string + numeric

Solution 6 - Bash

[ $b == 5 ] && { a=$c; true; } || a=$d

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

Solution 7 - Bash

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

Solution 8 - Bash

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

Solution 9 - Bash

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.

Solution 10 - Bash

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

a=$(( b == 5 ? 123 : 321  ))

Solution 11 - Bash

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"

Solution 12 - Bash

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)

Solution 13 - Bash

(ping -c1 localhost&>/dev/null) && { echo "true"; } || {  echo "false"; }

Solution 14 - Bash

You can use this if you want similar syntax

a=$(( $((b==5)) ? c : d ))

Solution 15 - Bash

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

Solution 16 - Bash

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.

Solution 17 - Bash

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

Solution 18 - Bash

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

Solution 19 - Bash

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

Solution 20 - Bash

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

Solution 21 - Bash

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'

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEn_t8View Question on Stackoverflow
Solution 1 - Bashghostdog74View Answer on Stackoverflow
Solution 2 - BashVladimirView Answer on Stackoverflow
Solution 3 - BashJWLView Answer on Stackoverflow
Solution 4 - Bashivan_pozdeevView Answer on Stackoverflow
Solution 5 - BashdutChView Answer on Stackoverflow
Solution 6 - BashSir AthosView Answer on Stackoverflow
Solution 7 - BashSujay U NView Answer on Stackoverflow
Solution 8 - BashJasonovichView Answer on Stackoverflow
Solution 9 - BashemuView Answer on Stackoverflow
Solution 10 - BashAndre DiasView Answer on Stackoverflow
Solution 11 - BashBrad ParksView Answer on Stackoverflow
Solution 12 - BashStefan HaberlView Answer on Stackoverflow
Solution 13 - BashwibbleView Answer on Stackoverflow
Solution 14 - BashPriyesh PatelView Answer on Stackoverflow
Solution 15 - BashjasonleonhardView Answer on Stackoverflow
Solution 16 - Bashdx_over_dtView Answer on Stackoverflow
Solution 17 - BashSergio AbreuView Answer on Stackoverflow
Solution 18 - BashBruno BronoskyView Answer on Stackoverflow
Solution 19 - BashAndré HillaireView Answer on Stackoverflow
Solution 20 - Bashdruid62View Answer on Stackoverflow
Solution 21 - BashDan BrayView Answer on Stackoverflow