command concatenation in bash - bash

I want to invoke a function with some input checks (the input should be an integer between 1-21). If ok, then do the echo "invoke", else just print a message about invalid input.
I tried with the following simplified example, it works for the invalid case, but does not invoke for the valid case. what is wrong?
function _check_num ()
{
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 -a "$1" -le 21 ] || echo "input should be (1-21)" && return 1 // one-liner
}
function _call()
{
_check_num $1 && echo "invoke only if input is 1-21" // does not invoke given valid input
}
Note: please explain me the root cause of this one-liner case.

Do not use chains of && and || as a replacement for an if statement.
&& and || have equal precedence, so a && b || c runs c if either a or b fail; it is not equivalent to if a; then b; else c; fi.
a && b || c && d is parsed as ((a && b) ||c) && d, not (a && b) || (c && d).
Use an expicit if statement to make your code readable. (Also, don't use -a inside [...]; it is considered ambiguous and obsolete.)
function _check_num ()
{
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 21 ]; then
return 0
else
echo "input should be (1-21)" >&2
return 1
fi
}
The less readable version would be something like the following, uses braces to properly group the commands.
function _check_num ()
{
{
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 21 ]
} || {
echo "input should be (1-21)" >&2 && return 1
}
}

function _check_num ()
{
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 -a "$1" -le 21 ]; then
return 0
else
echo "input should be (1-21)"
return 1
fi
}
I thin you missed your return 0 statement
See chepner answer: problem is priority of && / || operators
Normally the return status of the first part should be 0 but idk something could go wrong.

You can use group last echo and return in {...} to make it work:
function _check_num () {
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 -a "$1" -le 21 ] ||
{ echo "input should be (1-21)" && return 1; }
}
Without {...} last return 1 is always returning 1 whether value is valid or invalid.

If you put your "one liner" inside a script to test it:
function _check_num () {
[[ "$1" =~ ^[0-9]+$ ]] &&
[ "$1" -ge 1 -a "$1" -le 21 ] ||
echo "input should be (1-21)" &&
return 1 # one-liner
}
_check_num "$1"; echo "exit value $?"
we get:
$ ./script.sh qwe
input should be (1-21)
exit value = 1
$ ./script.sh 112
input should be (1-21)
exit value = 1
$ ./script.sh 12
exit value = 1
As you can see, the exit value is always 1 (not a successful result).
Therefore you can not use the exit value as the trigger for other code.
Where?
The core issue is in this structure:
[ "$1" -ge 1 -a "$1" -le 21 ] || echo "input should be (1-21)" && return 1
which could be reduced to:
[ … ] || echo && return 1
The two possible exit values (success or not) of the […] could be tested with:
f(){ true || echo && return 1; }; f; echo "$?" # prints a 1.
f(){ false || echo && return 1; }; f; echo "$?" # *also* a 1[1].
[1] After also printing a blank line from the internal echo.
Why ?
Because of the "short circuit" effect of the shell «AND and OR lists»
From bash manual:
Lists
Of these list operators, && and || have equal precedence …
The return status is the exit status of the last command executed.
AND and OR lists are sequences of one of more pipelines separated by the && and || control operators, respectively.
What is crucial to understand the issue is this part (for AND):
An AND list has the form
command1 && command2
command2 is executed if, and only if, command1 returns an exit status of zero.
And for OR
An OR list has the form
command1 || command2
command2 is executed if and only if command1 returns a non-zero exit status.
And the natural understanding of which should be the present exit status at any position:
The return status of AND and OR lists is the exit status of the last command executed in the list.
If you split any AND and OR list at any position, the exit status at such position is the exit status of the last previous command executed.
Some commands may get bypassed.
Step by step:
The first command is a test [[…]], the exit value at this point is its exit value.
This first command is connected to the next with an OR (||).
If the exit value of the first command is fail (not 0) the next command (echo) will be executed.
The exit code of echo is always success.
The next connection is an AND (&&).
At that point the exit value is true, the next command will be executed.
If the exit value of the first command is success (0) the next command will not be executed.
But the next one (return 1) will because the connection is an AND.
In both cases the return 1 is the last command executed.
The return value is always 1.
Precedence
Precedence may affect the order of the commands executed, but that does not explain why some commands are not executed.
Precedence in an "AND and OR list" is the same for && and ||.
So, operators will be considered in the left to right order they are found.
Associativity
Associativity in a shell "AND and OR list" is "left-associative".
That means that: if there are no parenthesis, operations are grouped from the left.
The first command is grouped with the second.
The result of that is grouped with the third, etc.
But even grouped as explained here. That does not explain why some commands are bypassed.
References
Advanced Bash-Scripting Guide: Chapter 26. List Constructs
Precedence
Operator associativity
Maybe you should consider using a function like:
function _check_num () {
declare -i num
if [[ $1 =~ ^[+]?(0+)?([0-9]+)$ ]]; then
num=${BASH_REMATCH[2]}
if ! (( num >=1 && num <= 21 )); then
echo "input should be (1-21)"
return 1
fi
return 0
else
echo "input should be a number"
return 2
fi
}

Related

How can I use the ternary operator in the bash script? [duplicate]

Is there a way to do something like this
int a = (b == 5) ? c : d;
using 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"
Code:
a=$([ "$b" == 5 ] && echo "$c" || echo "$d")
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".
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
[ $b == 5 ] && { a=$c; true; } || a=$d
This will avoid executing the part after || by accident when the code between && and || fails.
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. :)
There's also a very similar but simpler syntax for ternary conditionals in bash:
a=$(( b == 5 ? 123 : 321 ))
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"
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"; }
You can use this if you want similar syntax
a=$(( $((b==5)) ? c : d ))
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.
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
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"`
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
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
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 ))
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 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
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
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'
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.

Script enters all conditionals

I'd like to write a script that takes 2 parameters,
The first is parameter that could be "alpha/beta/tcp_friendliness/fast_convergence".
The Second should be a number for the alpha/beta cases and a 0/1 for the other 2.
for example: ./manage_cc alpha 512
Now i've wrote the following script that supposedly covers my cases, but it seems to go into all the conditionals. surely my syntax is broken so any help would be appreciated.
#!/bin/bash
echo -n $2 > /sys/module/tcp_tuner/parameters/$1
if [["$1" == ""] || ["$2" == ""]]
then
echo "You need to pass a property to modify as a first parameter and a value as the second"
fi
if [["$1" == "alpha"] || ["$1" == "beta"]]
then
echo -n $2 > /sys/module/tcp_tuner/parameters/$1
else
if [["$1" == "tcp_friendliness"] || ["$1" == "fast_convergence"]]
then
if [["$2" != "0"] && ["$2" != "1"]]
then
echo "This parameter only accepts a boolean value (0/1)"
exit 1
else
echo -n $2 > /sys/module/tcp_tuner/parameters/$1
fi
else
echo "The only accepted values for first parameter are alpha/beta/tcp_friendliness/fast_convergence"
exit 1
fi
fi
A rewrite of your code:
#!/usr/bin/env bash
write() {
printf "%s" "$2" > "/sys/module/tcp_tuner/parameters/$1"
}
die() {
echo "$*" >&2
exit 1
}
main() {
[[ -z $2 ]] && die "You need to pass a property to modify as a first parameter and a value as the second"
case $1 in
alpha|beta)
write "$1" "$2"
;;
tcp_friendliness|fast_convergence)
if [[ "$2" == "0" || "$2" == "1" ]]; then
write "$1" "$2"
else
die "This parameter only accepts a boolean value (0/1)"
fi
;;
*) die "The only accepted values for first parameter are alpha/beta/tcp_friendliness/fast_convergence"
;;
esac
}
main "$#"
Your condition syntax is wrong. When you start a condition with [[ you have to end it with ]], not just ]. They don't nest like parentheses.
if [[ "$1" == "alpha" || "$1" == "beta" ]]

Exit always called - variable precedence

I'm new to bash and having an issue where exit is always called in my script. Consider this simple code:
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
/export/home/scripts/script1.sh \
"$x" \
|| echo "Error.. something went wrong." && exit 1
fi
How can I handle errors, considering && takes precedence over || ?
Using GNU bash, version 3.2.51(1).
Thanks
You can do it like this :
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
/export/home/scripts/script1.sh \
"$x" \
|| { echo "Error.. something went wrong." && exit 1 ; }
fi
Note : I used { ; }, instead of (), because () will open your command in a subshell, so it will not exit.
&& and || have the same precedence in shell; the implicit parenthesization is (a || b) && c, not a || (b && c). Mixing || and && in the same list is rarely a good idea; use an explicit if statement.
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
if ! /export/home/scripts/script1.sh "$x"; then
echo "Error.. something went wrong"
exit 1
fi
fi
For arithmetic comparisons, prefer the arithemetic command ((...)) over [[ ... ]] for readability.
if (( x >= 1 && x <= 4 )); then
You can use braces to regroup commands without creating a new subshell :
{ true || false; } && echo true || echo false # echoes true
{ false || false; } && echo true || echo false # echoes false
Its syntax is pretty annoying : the opening brace must be followed by a space (or another character of $IFS, such as a linefeed or a tab), and the closing brace must be preceded by a linefeed or a ;, denoting the end of the last command of the block.
Parenthesis don't have those difficulties, but they will execute their instructions in a subshell, which has multiple other effects :
calling exit will only exit the subshell, not the shell running your script : (exit) is a no-op
updating variables will only apply to the subshell and will have no effect on the values known to your script : a=0;( (( a++ )) ; echo $a) ; echo $a will echo 1 from the subshell, then 0 from the outer shell.
I prefer doing explicit tests on scripts using if so that I can clean up after myself if things go pear shaped. Helps keep the code looking cleaner, too.
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
if ! /export/home/scripts/script1.sh "$x"; then
err="Error.. something went wrong."
test -t 0 && echo "$err" >&2 # send errors to stderr if on terminal
logger -p local0.critical -t $(hostname -s) "$err" # send to syslog
# You could even add some code here to clean up after script1.sh.
exit 1
fi
fi

zsh - read user input with default value - empty input not empty?

I wrote a function that asks for user input like this:
function is_confirmed {
read -rs -k 1 ans
if [[ "${ans}" == "n" || "${ans}" == "N" ]]; then
printf "No\n"
return 1
fi
if [[ "${ans}" == "y" || "${ans}" == "Y" ]]; then
printf "Yes\n"
return 0
fi
# here is my actual problem!!! this doesnt work when user input is blank!
if [[ "${ans}" == "" ]]; then
printf "Yes!\n"
return 1
fi
# Output is Damn!
printf "Damn"
return 1
}
works great so far, however, I want to set "yes" as the default answear, so when the user inputs nothing and just presses enter, it should fall back to "yes", so I tried it with || "$ans" == "" but that still falls back to "Damn"
how come? When I echo $ans at the end of the function it is empty...
EDIT 1:
This is what happens:
e_ask "Are you sure you want to install?\nWarning: This may override some files in your home directory."
if is_confirmed; then
echo "Great!"
else
e_error "Aborting..."
fi
here are the functions:
function e_ask {
printf "\n$1\n"
printf "(Y/n): "
}
function e_warn {
printf "Warning: $1\n"
}
function e_error {
printf "Error: $1\n"
exit 1
}
A case is better when you want to consider many different values and a default one
function is_confirmed {
read -rs -k 1 ans
case "${ans}" in
y|Y|$'\n')
printf "Yes\n"
return 0
;;
*) # This is the default
printf "No\n"
return 1
esac
}
Just add default variable in the beginning of function:
function is_confirmed {
ans="y"
echo "Put your choice:"
read $ans
}
If nothing is typed in, default value will remain.
The problem lies in how you wrote your conditions using the [[ builtin.
You need to change your conditions to:
function is_confirmed {
read -rs -k 1 ans
if [[ "${ans}" == "n" ]] || [[ "${ans}" == "N" ]]; then
printf "No\n"
return 1
else if [[ "${ans}" == "y" ]] || [[ "${ans}" == "Y" ]]; then
printf "Yes\n"
return 0
else if [[ -z "${ans}" ]]; then
printf "Empty\n"
else
printf "Damn\n"
fi
return 1
}
To explain, the [[ builtin tests just one condition, but you can chain multiple instances of [[ with && and ||. Your code, in contrast, tried to test 2 conditions inside [[ and used || as it's C/C++ usage.
More details available in help [[ or man bash

Ternary operator (?:) in Bash

Is there a way to do something like this
int a = (b == 5) ? c : d;
using 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"
Code:
a=$([ "$b" == 5 ] && echo "$c" || echo "$d")
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".
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
[ $b == 5 ] && { a=$c; true; } || a=$d
This will avoid executing the part after || by accident when the code between && and || fails.
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. :)
There's also a very similar but simpler syntax for ternary conditionals in bash:
a=$(( b == 5 ? 123 : 321 ))
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"
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"; }
You can use this if you want similar syntax
a=$(( $((b==5)) ? c : d ))
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.
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
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"`
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
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
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 ))
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 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
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
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'
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.

Resources