1. Overview

When we work on the command-line, we may need some procedures to run only if a condition is satisfied. Let’s imagine an example: run a list of commands depending on the characteristics of a file, let’s say: if it exists, the size, the name, the type, etc.

GNU/Linux provides us a very powerful set of tools that help us to achieve that.

In this tutorial, we’ll discuss conditional expressions and we’ll construct some examples to clarify the use of these expressions.

2. Conditional Expressions with && and ||

Let’s keep in mind that every command in our shell is a conditional expression. We know that because each one returns an integer, as an exit status, which indicates success – 0 or failure – 1.

When we’re using a shell, we usually run multiple instructions chained together using tokens like “;“, &, &&, or ||.

The && and || tokens allow us to connect our commands with the help of their exit status.

The && token chains the commands executing what is on the right if and only if the instruction on the left had an exit status of zero.

And the || token, chain the commands executing what is on the right if and only if the instruction on the left had an exit status different from zero.

Let’s create a quick example to clarify:

$ (exit 0) && echo True
True
$ (exit 1) || echo False
False

Now that we know about these tokens, let’s see how we can use it.

First, let’s create a file, named some_file, with the following content:

$ cat << __end > some_file
foo
this string exists
bar
__end

Now, with the use of the grep command, let’s see how we can display a text if the string is present in the file:

$ grep -q "this string exists" some_file && { echo "Everything"; echo "is all right"; }

As the first instruction has an 0 exist status, the output of this command-line will be:

Everything
is all right

Now, let’s try with:

$ grep -q "this string doesn't exist" some_file || { echo "Not"; echo "today"; }

This time, the string we searched for doesn’t exist int he file, so the first instruction returns with a 1 – failure exit status. This means the output of this command-line will be:

Not
today

Keeping this in mind, it makes us think that we can compose commands by chaining them with these tokens.

Let’s try using the same file and the same commands:

$ grep -q "this string doesn't exist" some_file \
&& { echo "Everything"; echo "is all right"; } \
|| { echo "Not"; echo "today"; }

Let’s take a closer look at the expression.

Here, we use the -q parameter to just get the exit status and not the usual output in the stdout.

With that exit status:

  • If it’s 0 (if the string is in the file), then, the list of commands right of the && token will execute
  • If the exit status is not 0 (if the string is not in the file), then, what’s on the right of the token && will not execute; instead the instructions to the right of the token || will be executed

So, the result of this command will be:

Not
today

Let’s see another example using these tokens to chain commands:

$ ( echo "Here 1"; exit 1 ) \
    && ( echo "Here 2" ) \
    && ( echo "Here 3" ) \
    || ( echo "Here 4"; exit 4 ) \
    && ( echo "Here 5"; exit 5 ) \
    || ( echo "Here 6"; exit 6 ) \
    || ( echo "Here 7"; exit 0 ) \
    && ( echo "Here 8" )

And the output will be:

Here 1
Here 4
Here 6
Here 7
Here 8

To sum up, we can say that the && and the || token are equivalent to the AND and OR logical operations, that allow us to control the course of an instruction flow. But we can also use it (very carefully) as an if-else syntax.

3. Building More Complex Expressions with if

In the previous examples, we saw that we can articulate a list of commands thanks to the fact that the commands are conditional expressions. However, this syntax can be tricky for more complex commands.

To keep things clearer, and to create new ways of articulate the flow of instructions, our shells have the keywords: if, then, elif, else, fi.

So, for example, let’s use the if, then, else, and fi keywords:

$ if
    ( echo "Here 1" )
    ( echo "Here 2" )
    ( echo "Here 3" )
then
echo Inside the \"then\" sentence
else
echo Inside the \"else\" sentence
fi

And the output will be:

Here 1
Here 2
Here 3
Inside the "then" sentence

Now, let’s try with the following:

$ if
    ( echo "Here 1" )
    ( echo "Here 2"; exit 2 )
    ( echo "Here 3"; exit 3 )
then
    echo Inside the \"then\" sentence
else
    echo Inside the \"else\" sentence
fi

And the output will be:

Here 1
Here 2
Here 3
Inside the "else" sentence

This is because this syntax focuses on the status of the last command executed. So that’s the reason why it prints “Here 3” after a command with a non-zero status, and also is the reason why the shell entered into the else block.

4. The [, test, [[, and ((, Tokens

The shells are commonly equipped with the tokens [, test, and [[ builtins that use the conditional expressions.

Each of these tokens differs from each other, but we’ll discuss their differences later. Right now, let’s focus on what [, test, and [[ have in common.

For our first example, let’s see how we can check if a file is a directory, using the standard /home directory:

$ [ -d /home ] && echo "It's a directory"
It's a directory

Here, we’ve used the -d option which checks if the file exists and is a directory, and we’ve surrounded the expression with the [ and ] tokens.

Next, let’s see another example which checks if 2 Strings are equal:

$ [ "this string" = "THIS STRING" ] \
    && echo They are equals \
    || echo They are different

And the output will be:

They are different

We can, of course, build a very similar example that compares integers, using any of the options: “-eq”, “-ne”, “-lt”, “-le”, “-gt”, or “-ge”.

Let’s again surround this expression with the [] tokens and use the -lt option to compare if one integer is smaller than the other:

$ [ 3 -lt 6 ] && echo "It's less than"
It's less than

It should be noted that the examples that we’ve listed can be built with any of the [, [[ or test commands.

Now, let’s dig a bit into what each token and command does and the differences between some of them.

4.1. The [ and test Builtins

[ and test are builtins that can help us perform the operations that we list in the past section.

With the [ builtin, we can surround a conditional expression. This will have the same validity as the test builtin.

In other words:

$ [ "string1" = "string2" ]

It’s almost the same as:

$ test "string1" = "string2"

4.2. The [[ Keyword

Compared to the previous tokens, the [[ is a keyword, which is different than a builtin.

A builtin is a command or a function that will be executed in the shell, instead of an external executable.

A keyword is a word that the shell considers that is special; so, if the shell finds the keyword [[, then the shell will look for the closing ]]. We can list them by typing compgen -k.

Also, the [[ keyword is an extension of some shells like Bash.

4.3. The (( Token

(( is an extension of some shells that allows us to evaluate an expression as an arithmetic one.

Let’s see an example of using this:

(( var < 10 ))
(( var++ ))
(( var=1; var<=10; var++ ))

This operator will have an exit status of 0 if the value of the expression is non-zero; otherwise, the return status is 1.

4.4. The Differences Between [[, and [

Let’s go over some of the main differences between [[ and [.

First, as we saw in the 4.1 and 4.2 sections, [[ is a keyword of our shell, but [, and test, are shell builtins

Another important difference is that[ is POSIX, while [[ is not.

when using the < or > operators, we need to escape them if we want to use them inside [:

$ [ "a" \< "b" ]

This is not the case if we want to use them inside the keyword [[.

Another notable difference is that the [[ keyword sorts lexicographically using the current locale, while [ uses ASCII ordering.

Next, the && and || operators will differ inside each pair of tokens.

While this will work:

$ [[ 1 = 1 && 2 = 2 ]] # returns True

This case will not:

$ [ 1 = 1 && 2 = 2 ] # returns a Bash error

But we can operate in the same way by doing the following:

$ [ 1 = 1 ] && [ 2 = 2 ]

That’s because, as we saw in the 2.1 section: every command in our shell is a conditional expression.

In other words: [ 1 = 1 ] returns true and, with the help of the token &&, then [ 2 = 2 ] will be executed and also return true. Then, the combined sentences will return true.

The tokens also differ in terms of word splitting and filename generation upon expansions.

If we define a variable like this:

$ var="a b"

Then, this will return true:

$ [[ $var = "a b" ]]

And this returns a syntax error because here expands as [ a b = ‘a b’ ]:

$ [ $var = "a b" ]
bash: [: too many arguments

The use of the token “=” also differs:

Let’s create two examples:

$ [ abc = a?? ] # returns False
$ [[ abc = a?? ]] # return True

This behavior is because, with the use of the builtin [, we perform a string comparison. On the other hand,  when using the [[ keyword, bash performs a pattern matching.

Finally, let’s note that [[ have the =~ operator.

While using the [[ token we can use the =~ token, but in [ we aren’t able to use it.

With the help of this operator, we can perform a comparison between a string and an extended regular expression. The string to the right of the operator will be considered an extended regular expression.

Let’s create two examples where we deal with regular expressions using this operator:

$ [[ Baeldung =~ .*e.* ]] && echo True || echo False 
True 
$ [[ Baeldung2 =~ ^(b|B).*g$ ]] && echo True || echo False 
False

To sum up, when to use one or the other depends on what we want to achieve.

As we can see, the [[ keyword has more options than the [ and test builtins, but the builtins are POSIX, so, using these, we can avoid losing portability.

5. A Quick Note About Spaces

As we saw, we can use these options to evaluate arguments, but let’s remember that we need to follow an easy syntax that requires us to add spaces between tokens.

For example, this example will work fine:

[ -e file ]

But each element of the following list will not:

[ -e file]
[-e file]
[-efile]

Let’s see what happen if we don’t separate each token with spaces:

$ [ 1=1] && echo True
bash: [: missing `]'

Now let’s try with the token [[:

$ [[ 1=1]] && echo True 
bash: conditional binary operator expected 
bash: syntax error near `True'

So, we must pay a little attention when we construct our sentences.

6. Conclusion

In this article, we’ve covered what a conditional expression is, and how we can build expressions using different constructs and keywords.

guest
0 Comments
Inline Feedbacks
View all comments