Bash Scripting Concepts: Part 2 of 2
Published: 2021-12-22 03:07:56

## Overview

This is the second part of the tutorial. If not done already, please read the first part: [Bash Scripting Concepts: Part 1 of 2](https://dev.to/serdigital64/bash-scripting-concepts-part-1-of-2-28fk)

## Working with loops

**Bash** provides three ways of creating loops:

- `for`: loop for a predefined number of times
- `while`: loop while the exit condition is true. The condition is evaluated before executing tasks.
- `until`: loop until the exit condition is true. The condition is evaluated after executing tasks.

In addition to the loop condition, Bash provides two statements that can be used to control the loop execution flow:

- `break`: forces the loop to stop.
- `continue`: forces the loop to skip remaining tasks and start the next iteration.

### Command: `while`

In the following example, the loop iterates 5 times. The loop condition is evaluated after all commands in the code block are executed:

```shell
#!/bin/bash

declare -i count=1
declare -i max=5

while ((count <= max)); do
  printf 'counter: %s\n' "${count}"
  count=$((count + 1))
done

```

### Command: `until`

Using the same structure as in the `while` example, notice that now the loop iterates 4 times only. This is because the loop condition is evaluated before executing the code block:

```shell
#!/bin/bash

declare -i count=1
declare -i max=5

until ((count == max)); do
  printf 'counter: %s\n' "${count}"
  count=$((count + 1))
done
```

### Command: `for`

In the case of the `for` loop the iteration is predefined. Instead of having a loop condition, the loop variable `count` will be assigned each value in the list:

```shell
#!/bin/bash

declare -i count

for count in 1 2 3 4 5 ; do
  printf 'counter: %s\n' "${count}"
done
```

## Working with conditionals

Bash provides the following options for implementing conditional execution:

- `||`: (logical OR) evaluates the execution of two commands and sets the exit status to zero if any associated exit status is zero.
- `&&`: (logical AND) evaluates the execution of two commands and sets the exit status to zero if all associated exit statuses are zero.
- `\!`: (logical NOT) evaluates the execution of a command and sets the exit status to zero if the associated exit status is not zero.
- `(( ))`: performs logical evaluation on the integer expression and sets the exit status to zero if true
- `[[ ]]`: evaluates the literal expression and sets the exit status to zero if true.
- `if`: executes a command and if the exit status is zero then performs additional actions.
- `case`: compares the provided value against a list of patterns and executes the commands upon match.

As mentioned before, **Bash** interprets the exit status of commands as:

- `0`: true
- `>0`: false

In the following examples `true` and `false` are external commands that emulates true and false values (exist status 0 and 1 respectively)

### Logical OR: `||`

```shell
#!/bin/bash

true || false
printf 'evaluation result of (true || false): %s\n' $?
true || true
printf 'evaluation result of (true || true): %s\n' $?
false || false
printf 'evaluation result of (false || false): %s\n' $?
false || true
printf 'evaluation result of (false || true): %s\n' $?
```

### Logical AND: `&&`

```shell
#!/bin/bash

true && false
printf 'evaluation result of (true && false): %s\n' $?
true && true
printf 'evaluation result of (true && true): %s\n' $?
false && false
printf 'evaluation result of (false && false): %s\n' $?
false && true
printf 'evaluation result of (false && true): %s\n' $?
```

### Logical NOT: `!`

```shell
#!/bin/bash

! true
printf 'evaluation result of (! true): %s\n' $?
! false
printf 'evaluation result of (! false): %s\n' $?
```

### Arithmetic Expression Evaluation: `(( ))`

The `(( ))` form accepts several logical operators. Some of them are:

- `==`: equal
- `!=`: not equal
- `>`: greater than
- `<`: less than
- `>=`: greater than or equal
- `<=`: less than or equal

```shell
#!/bin/bash

declare -i test=$RANDOM
(( ${test} > 5000 ))
printf 'evaluation result of (( %s > 5000 )): %s\n' ${test} $?
```

### Expression Evaluation: `[[ ]]`

The `[[ ]]` form accepts several logical operators and tests. Some of them are:

- `==`: equal
- `!=`: not equal
- `-z`: string is empty
- `-n`: string is not empty
- `-f`: path is a file

For `==` and `!=` the special character `*` can be used as a wildcard to match zero or more characters to the right.

```shell
#!/bin/bash

declare test="$RANDOM"
[[ "${test}" == 1* ]]
printf 'evaluation result of [[ "%s" == 1* ]]: %s\n' "${test}" $?
```

### Command: `if`

In the following example, arithmetic evaluation is used. Notice that quotes are not required within `(( ))`

```shell
#!/bin/bash

declare -i test=${RANDOM}
if (( ${test} >= 10000 )); then
  printf 'test value (%s) is equal or greater than 10000\n' ${test}
elif (( ${test} > 5000 || ${test} < 10000 )); then
  printf 'test value (%s) is between 5001 and 9999\n' ${test}
else
  printf 'test value (%s) is less than than 5001\n' ${test}
fi
```

### Command: `case`

```shell
#!/bin/bash

declare -i test=${RANDOM}
case ${test} in
  1*|2*) printf 'Random number (%s) starts with 1 or 2\n' ${test};;
  3*) printf 'Random number (%s) starts with 3\n' ${test};;
  *) printf 'Random number (%s) does not start with 1,2 or 3\n' ${test};;
esac
```

## Redirecting data flows

Bash provides two alternatives for establishing data flows:

- Redirection
  - Set read source for **STDIN**
  - Set write destination for **STDOUT**
  - Set write destination for **STDERR**
- Pipelines: integrate two commands by plugging the **STDOUT** from the first one to the **STDIN** of the second one

### Redirection

In the following example, two functions will communicate with each other using a common file:

```shell
#!/bin/bash

readonly DATA_BRIDGE="$(mktemp)"

function produce_data() {
  printf 'produce_data(): write data to the temporary file (%s) by redirectin the STDOUT of the printf command\n' "${DATA_BRIDGE}"
  printf '[sample data]\n' >"${DATA_BRIDGE}"
}

function ingest_data() {
  printf 'ingest_data(): read data from the temporary file (%s) by redirecting the STDIN of the cat command: ' "${DATA_BRIDGE}"
  cat < "${DATA_BRIDGE}"
}

produce_data
ingest_data

rm -f "${DATA_BRIDGE}"
```

### Pipelines

This example shows an alternative way of integrating both functions using pipelines:

```shell
#!/bin/bash

function produce_data() {
  printf '[sample data]\n'
}

function ingest_data() {
  cat
}

printf 'Integrate functions produce_data() and ingest_data() by piping their STDIN and STDOUT: '
produce_data | ingest_data

```

## Next Steps

Discover advanced features by exploring the [Bash Reference Manual](https://www.gnu.org/software/bash/manual/html_node/index.html#SEC_Contents):

- jobs
- signals
- traps
- parallelism
- error handling
- configuration settings

Organize your code by choosing a coding style. For example: [Google Shell Style Guide
](https://google.github.io/styleguide/shellguide.html)

Enhance script's quality by incorporating linter and testing tools:

- [Shell Check](https://www.shellcheck.net/)
- [Basts-Core](https://github.com/bats-core/bats-core)

## Copyright information

This article is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/). For copyright information on the product or products mentioned inhere refer to their respective owner.

## Disclaimer

Opinions presented in this article are personal and belong solely to me, and do not represent people or organizations associated with me in a professional or personal way. All the information on this site is provided "as is" with no guarantee of completeness, accuracy or the results obtained from the use of this information.