Bash Scripting Concepts: Part 1 of 2 @ SerDigital64 | Monday, Dec 6, 2021 | 7 | Tuesday, Dec 21, 2021

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/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'

1
2
3
4
5
6
7
8
#!/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:

1
2
3
4
5
#!/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:

1
2
3
4
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/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

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.

© 2021 - 2022 SerDigital64's Blog

Powered by Hugo with theme Dream.

Articles in this site are licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0)

avatar

SerDigital64's BlogTraveler log from my journey through the lands of the ever evolving digital world

About SerDigital64
███████╗███████╗██████╗                         
██╔════╝██╔════╝██╔══██╗                        
███████╗█████╗  ██████╔╝                        
╚════██║██╔══╝  ██╔══██╗                        
███████║███████╗██║  ██║                        
╚══════╝╚══════╝╚═╝  ╚═╝                        
                                  
██████╗ ██╗ ██████╗ ██╗████████╗ █████╗ ██╗     
██╔══██╗██║██╔════╝ ██║╚══██╔══╝██╔══██╗██║     
██║  ██║██║██║  ███╗██║   ██║   ███████║██║     
██║  ██║██║██║   ██║██║   ██║   ██╔══██║██║     
██████╔╝██║╚██████╔╝██║   ██║   ██║  ██║███████╗
╚═════╝ ╚═╝ ╚═════╝ ╚═╝   ╚═╝   ╚═╝  ╚═╝╚══════╝
                                  
██████╗  ██╗  ██╗                               
██╔════╝ ██║  ██║                               
███████╗ ███████║                               
██╔═══██╗╚════██║                               
╚██████╔╝     ██║                               
╚═════╝      ╚═╝                               


Solutions_Architect && SysAdmin && DevOpsEngineer
Developer = 'for_the_fun'
Linux && OSS_advocate
Sci_Fi = 'fan'
Photography && DIY == enthusiast()

eMail('serdigital64@gmail.com')
Articles in this site are licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0)