Bash Scripting Concepts: Part 2 of 2

serdigital64

SerDigital64

Posted on December 22, 2021

Bash Scripting Concepts: Part 2 of 2

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

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:

#!/bin/bash

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

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

Enter fullscreen mode Exit fullscreen mode

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:

#!/bin/bash

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

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

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:

#!/bin/bash

declare -i count

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

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

#!/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' $?
Enter fullscreen mode Exit fullscreen mode

Logical AND: &&

#!/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' $?
Enter fullscreen mode Exit fullscreen mode

Logical NOT: !

#!/bin/bash

! true
printf 'evaluation result of (! true): %s\n' $?
! false
printf 'evaluation result of (! false): %s\n' $?
Enter fullscreen mode Exit fullscreen mode

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
#!/bin/bash

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

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.

#!/bin/bash

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

Command: if

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

#!/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
Enter fullscreen mode Exit fullscreen mode

Command: case

#!/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
Enter fullscreen mode Exit fullscreen mode

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:

#!/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}"
Enter fullscreen mode Exit fullscreen mode

Pipelines

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

#!/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

Enter fullscreen mode Exit fullscreen mode

Next Steps

Discover advanced features by exploring the Bash Reference Manual:

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

Organize your code by choosing a coding style. For example: Google Shell Style Guide

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

Copyright information

This article is licensed under a Creative Commons Attribution 4.0 International License. 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.

💖 💪 🙅 🚩
serdigital64
SerDigital64

Posted on December 22, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related