Bash Scripting Concepts: Part 1 of 2
Tags: bash
Published: 2021-12-22 03:06:52

## Overview

**Bash** is a tool that provides a text-based user interface for managing operating system resources. It's also the run-time environment for a simple interpreted programming language that can be used to create **scripts** for automating tasks.

**Bash** is the evolution of the popular **Born Shell (sh)**, a de facto standard among Unix-like operating systems. It provides all the features of its predecessor plus a new set of built-ins and configuration settings.

## Using Bash

There are three common use cases for **Bash**:

- **User shell**: configured at the operating system level, provides the shell for user login, either local or remote.
- **Script run-time**: provides the execution run-time for **Bash** scripts
- **Temporal shell**: provides an interactive shell session on top of the user shell. The new session is a sub-process of the actual user or a different user when using privilege delegation tools such as SuDO.

This tutorial will focus on the **scripts run-time** use case.

## Script structure

**Bash** scripts are text files describing a sequence of commands:

- Commands can be either built-in or external Linux apps.
- Commands are separated from each other by the new-line character (standard line separator for Linux/Unix systems).
- Long lines can be split using the scape character `\`
- Commands can be grouped on the same line using the semicolon separator `;`
- Commands and arguments are separated using one or more spaces.
- Although not required, it's recommended that the first line of the script contains: `#!/bin/bash`
- Script files must have execution and read permissions if used directly, or just read-only permissions if called using **Bash**.

For example:

```shell
#!/bin/bash

echo 'echo and printf are built-in commands'

printf 'pwd is an external Linux command that shows the current directory: '
pwd

printf 'multiple '; printf 'commands '; printf 'on the same line\n'

printf '%s %s %s\n' \
  'single command' \
  'using multiple' \
  'lines'
```

## Using Variables

### Declaration

Variables in Bash are created using a declaration command and the equal `=` symbol to associate the variable name to its value. Notice that there should be no spaces before and after the assignment symbol:

> `declare variable_name='variable value'`

Bash provides the following built-in commands for declaring variables:

| Command    | Usage                                  | Scope                         |
| ---------- | -------------------------------------- | ----------------------------- |
| `export`   | Declare global variables               | Script-wide and sub-processes |
| `readonly` | Declare readonly variables (constants) | Script-wide                   |
| `declare`  | Declare script level variables         | Script-wide                   |
| `local`    | Declare function level variables       | Function-wide                 |

Consider the following best practices:

- Declare variables at the beginning of the code block.
- Declare and initialize all the variables that the script will use.
- Readonly and exported variables should be named using all uppercase.
- Use single quotes for static content.

### Retreive variable's value

To use the value of a variable, prepend the dollar symbol `$` to the name of the variable surrounded by brackets `{}`.

As a best-practice, surround variable expansion with double quotes:

> `echo "${test_variable}"`

**Bash** also provides processing functions that can be used for altering the value before the expansion.

For example:

```shell
#!/bin/bash

declare var1='word1'
declare var2='WORD2'
declare var3='AbCdE/12345'

printf 'replace value with its length: "${#var1}" = "%s"\n' "${#var1}"
printf 'right strip from pattern: "${var3##*/}" = "%s"\n' "${var3##*/}"
printf 'left strip from pattern: "${var3%%/*}" = "%s"\n' "${var3%%/*}"
printf 'convert to uppercase: "${var1^^}" = "%s"\n' "${var1^^}"
printf 'convert to lowercase: "${var2,,}" = "%s"\n' "${var2,,}"
```

### Data Types

Bash supports four data types:

| Type              | Declaration command |
| ----------------- | ------------------- |
| string            | declare             |
| integer           | declare -i          |
| array             | declare -a          |
| associative array | declare -A          |

Even though there is no explicit boolean data type, **Bash** interprets the exit status of commands as:

- exit status == 0: true
- exit status > 0: false

### Parameters

Parameters are special variables that are automatically created when a function or script is called with arguments.

The variable name is created by using a single number to represent the position of the word in the argument list:

> `bash say_hello.bash 'to' 'my' 'little' 'friend'`

```shell
#!/bin/bash

declare msg1="$1" # Variable $1: assigned to the first variable
declare msg2="$2" # Variable $2: assigned to the second variable
declare msg3="$3" # Variable $3: assigned to the third variable
declare msg4="$4" # Variable $4: assigned to the forth variable

printf 'say hello %s %s %s %s\n' "${msg1}" "${msg2}" "${msg3}" "${msg4}"
```

### Shell Expansion

In addition to variables **Bash** provides additional features for generating dynamic values:

- `$( )`: Command Expansion: Execute the command in a sub-process and expand its STDOUT.
- `$(( ))`: Arithmetic Expansion: Evaluate the arithmetic expression and use the resulting value for the expansion.
- `$(< )`: File Expansion: Read the content of a file and use it for the expansion.

For example:

```shell
#!/bin/bash

printf 'Command Expansion: "$(echo hello-world)" = "%s"\n\n' "$(echo 'hello-world')"
printf 'Arithmetic Expansion:  "$(( 2 + 2 ))" = %s\n\n' "$(( 2 + 2 ))"
printf 'File Expansion: "$(</etc/os-release)"\n %s\n' "$(</etc/os-release)"
```

## Working with processes

Before going into the details, let's review the following key concepts about Unix processes:

- A process is a running app that is executed from an already running process (parent). For scripts, the parent process is the one running the **Bash** run-time.
- The Operating System assigns unique integer identifiers to each process (**PID**: Process ID).
- Processes have 3 data paths:
  - **STDIN**: standard input: process can read data from this path.
  - **STDOUT**: standard output: process can write data to this path.
  - **STDERR**:: standard error: process can write error diagnostic data to this path.
- Exit status: numeric value that represents the final execution status of the process. In general:
  - `0`: successful execution
  - `>0`: failed execution. The app can assign different numbers to further describe the error.

**Bash** provides the following features for interacting with processes:

- Variables: show process information
  - `$?`: exit status of the last executed command
  - `$BASHPID`: PID of the current **Bash** process
- Redirection: redirect the data flow from the STDIN, STDOUT, and STDERR
- Pipelines: integrate two processes by creating a pipe between the STDOUT from one to the STDIN of the other

Additional features are available but no covered in the current tutorial (jobs, signals, traps, parallelism, etc.)

## Implementing Functions

To declare a function in Bash use the following structure:

```shell
function <FUNCTION_NAME>() {
  <COMMANDS>
  return <EXIT_STATUS>
}
```

Functions in Bash behave in a similar way to scripts and commands:

- Can be called directly from the script or command line
- Use positional parameters that are automatically assigned to `$N` variables
- Have exit status
- Can send data to STDOUT and STDERR
- Can receive data from STDIN
- Can be used in complex command sequences (`&&`, `||`, `|`)

Let's create a function with all the concepts seen so far:

```shell
#!/bin/bash

readonly TEST_READONLY='content of this variable is constant and can not be modified afterwords'
export TEST_EXPORT='content of this variable is visible everywhere'
declare test_declare='this variable is declared at the script level'
declare -i function_result=0

function test_function() {

  local test_parameter="$1"
  local test_parameter_default="${2:-default-value}"

  local test_local='this variable is available only inside this function'
  local test_static=''  # initialize a static variable
  local test_dynamic='' # initialize a dynamic variable

  test_static='this variable has static content'
  test_dynamic="$(echo 'this dynamic variable is assigned at execution time')"

  printf 'Show variable content from inside a function:\n\n'
  printf '  first parameter: [%s]\n' "${test_parameter}"
  printf '  second parameter, default value: [%s]\n' "${test_parameter_default}"
  printf '  local static variable: [%s]\n' "${test_static}"
  printf '  local dynamic variable: [%s]\n' "${test_dynamic}"
  printf '  exported variable: [%s]\n' "${TEST_EXPORT}"
  printf '  readonly variable: [%s]\n' "${TEST_READONLY}"
  printf '  declared variable: [%s]\n' "${test_declare}"

  return 5

}

test_function 'this value is assigned to the first parameter'
function_result=$?

printf '\nShow the same variables but outside the function:\n\n'
  printf '  local variable: [%s]\n' "${test_local}"
  printf '  local static variable: [%s]\n' "${test_static}"
  printf '  local dynamic variable: [%s]\n' "${test_dynamic}"
  printf '  exported variable: [%s]\n' "${TEST_EXPORT}"
  printf '  readonly variable: [%s]\n' "${TEST_READONLY}"
  printf '  declared variable: [%s]\n' "${test_declare}"

printf '\nShow the exit status (return value) of the function: [%s]\n' "${function_result}"
printf '\nShow current PID of the Bash run-time: [%s]\n' "${BASHPID}"
```

## Next Steps

Continue reading the second part of the tutorial: [Bash Scripting Concepts: Part 2 of 2](https://dev.to/serdigital64/bash-scripting-concepts-part-2-of-2-3j0f)

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