bash (Bourne Again shell) is the standard GNU shell, a powerful tool for the advanced and professional user.
This shell is a so-called superset of the Bourne shell, a set of add-ons and plug-ins.
This means that the Bourne Again shell is compatible with the Bourne shell: commands that work in sh, also work in bash.

However, the reverse is not always the case.

The bash interprets user commands, which are either directly entered by the user, or which can be read from a file called the shell script or shell program.

Shell scripts are interpreted, not compiled, so the shell reads commands from the script line per line and searches for those commands on the system.

Below my own brief collection of notes about bash and bash scripting.


Variables

There are no data types. A variable in bash can contain a number, a character, a string of characters.

You have no need to declare a variable, just assigning a value to its reference will create it.

#!/usr/bin/env bash

NAME="Andrea"
echo "Hello $NAME!"

Some more examples:

NAME="Andrea"
echo $NAME
echo "$NAME"
echo "${NAME}!"
String quotes
NAME="Andrea"
echo "Hi $NAME" #=> Hi Andrea
echo 'Hi $NAME' #=> Hi $NAME
Shell execution
echo "I'm in $(pwd)"
echo "I'm in `pwd`" # Same
Conditional execution
git commit && git push
git commit || echo "Commit failed"

Parameter expansions

Basics

The ‘$’ character introduces parameter expansion, command substitution, or arithmetic expansion. The parameter name or symbol to be expanded may be enclosed in braces, which are optional but serve to protect the variable to be expanded from characters immediately following it which could be interpreted as part of the name.

name="Andrea"
echo ${name}
echo ${name/A/a}    #=> "andrea" (substitution)
echo ${name:0:2}    #=> "An" (slicing)
echo ${name::2}     #=> "An" (slicing)
echo ${name::-1}    #=> "Andre" (slicing)
echo ${food:-Cake}  #=> $food or "Cake"
length=2
echo ${name:0:length} #=> "An"
STR="/path/to/foo.cpp"
echo ${STR%.cpp}         # /path/to/foo
echo ${STR%.cpp}.o       # /path/to/foo.o

echo ${STR##*.}          # cpp (extension)
echo ${STR##*/}          # foo.cpp (basepath)

echo ${STR#*/}           # path/to/foo.cpp
echo ${STR##*/}          # foo.cpp

echo ${STR/foo/bar}      # /path/to/bar.cpp
STR="Hello world"
echo ${STR:6:5}          # "world"
echo ${STR:-5:5}         # "world"
SRC="/path/to/foo.cpp"
BASE=${STR##*/}          #=> "foo.cpp" (basepath)
DIR=${SRC%$BASE}         #=> "/path/to" (dirpath)
Substitution
${FOO%suffix}            Remove suffix
${FOO#prefix}            Remove prefix
${FOO%%suffix}           Remove long suffix
${FOO##prefix}           Remove long prefix
${FOO/from/to}           Replace first match
${FOO//from/to}          Replace all
${FOO/%from/to}          Replace suffix
${FOO/#from/to}          Replace prefix
printf
printf "Hello %s, I'm %s: my bowl is empty!" Andrea Ivo #=> "Hello Andrea, I'm Ivo: my bowl is empty!

Comments

# Single line comment

: '
This is a
multi line
comment
'
Substrings
${parameter:offset:length}

The Substring Expansion expands to up to length characters of the value of parameter starting at the character specified by offset.

Example:

${FOO:0:3}   Substring (position, length)
${FOO:-3:3}  Substring from the right
Length
${#FOO}  Length of $FOO
Default values
${FOO:-val}      $FOO, or val if not set
${FOO:=val}      Set $FOO to val if not set
${FOO:+val}      val if $FOO is set
${FOO:?message}  Show error message and exit if $FOO is not set

Loops

Basic 'for' loop

The for loop is a little bit different from other programming languages. Basically, it let's you iterate over a series of 'words' within a string.

for i in /etc/rc.*; do
  echo $i
done
Ranges
for i in {1..5}; do
  echo "Welcome $i"
done

With step size

for i in {5..50..5}; do
  echo "Welcome $i"
done
Reading lines with 'while' loop
cat file.txt | while read line; do
  echo $line
done
Endless 'while' loop
while true; do
  ···some code...
done

Functions

As in almost any programming language, you can use functions to group pieces of code in a more logical way or practice the divine art of recursion.

Defining functions
myfunc() {
  echo "hello $1"
}

Alternate syntax:

function myfunc() {
  echo "hello $1"
}
Returning values
myfunc() {
  local myresult='some value'
  echo $myresult
}

result=$(myfunc)
Raise errors
myfunc() {
  return 1
}

if myfunc; then
  echo "success"
else
  echo "failure"
fi
Arguments
$#   Number of arguments
$*   All arguments
$@   All arguments, starting from first
$1   First argument
Trap errors
trap 'echo Error at about $LINENO' ERR

or

traperr() {
  echo "ERROR: ${BASH_SOURCE[1]} at about ${BASH_LINENO[0]}"
}
set -o errtrace
trap traperr ERR

Conditionals

Conditionals let you decide whether to perform an action or not by evaluating an expression.

Basic conditions
[ -z STRING ]           Empty string
[ -n STRING ]           Not empty string
[ NUM -eq NUM ]         Equal
[ NUM -ne NUM ]         Not equal
[ NUM -lt NUM ]         Less than
[ NUM -le NUM ]         Less than or equal
[ NUM -gt NUM ]         Greater than
[ NUM -ge NUM ]         Greater than or equal

[[ STRING =~ STRING ]]  Regexp

(( NUM < NUM ))         Numeric conditions

[ ! EXPR ]              Not
[ X ] && [ Y ]          And
[ X ] || [ Y ]          Or
File conditions
[ -e FILE ]             Exists
[ -r FILE ]             Readable
[ -h FILE ]             Symlink
[ -d FILE ]             Directory
[ -w FILE ]             Writable
[ -s FILE ]             Size is > 0 bytes
[ -f FILE ]             File
[ -x FILE ]             Executable
[ FILE1 -nt FILE2 ]     1 is more recent than 2
[ FILE1 -ot FILE2 ]     2 is more recent than 1
[ FILE1 -ef FILE2 ]     Same files
Case/switch
case "$1" in
  start | up)
    vagrant up
    ;;

  *)
    echo "Usage: $0 {start|stop|ssh}"
    ;;
esac
Other examples
# String
if [ -z "$string" ]; then
  echo "String is empty"
elif [ -n "$string" ]; then
  echo "String is not empty"
fi

# Combinations
if [ X ] && [ Y ]; then
  ...some code...
fi

# Regex
if [[ "A" =~ "." ]]

if (( $a < $b ))

if [ -e "file.txt" ]; then
  echo "file exists"
fi

Arrays

Bash provides one-dimensional indexed and associative array variables.

Any variable may be used as an indexed array and there is no maximum limit on the size of an array.

Defining arrays
Fruits=('Apple' 'Banana' 'Orange')

Fruits[0]="Apple"
Fruits[1]="Banana"
Fruits[2]="Orange"
Working with arrays
echo ${Fruits[0]}     # Element #0
echo ${Fruits[@]}     # All elements, space-separated
echo ${#Fruits[@]}    # Number of elements
echo ${#Fruits}       # String length of the 1st element
echo ${#Fruits[3]}    # String length of the Nth element
echo ${Fruits[@]:3:2} # Range (from position 3, length 2)
Operations
Fruits=("${Fruits[@]}" "Watermelon")     # Push
Fruits=( ${Fruits[@]/Ap*/} )             # Remove by regex match
unset Fruits[2]                          # Remove one item
Fruits=("${Fruits[@]}")                  # Duplicate
Fruits=("${Fruits[@]}" "${Veggies[@]}")  # Concatenate
lines=(`cat "logfile"`)                  # Read from file
Iteration
for i in "${arrayName[@]}"; do
   echo $i
done

Options

Some useful option

set -o noclobber    # Avoid overlay files (echo "hi" > foo)
set -o errexit      # Used to exit upon error, avoiding cascading errors
set -o pipefail     # Unveils hidden failures
set -o nounset      # Exposes unset variables

In the debug phase, it could be usefull add the -x option:

#!/bin/bash -x

That produces interesting output information.


History

Commands
history                Show history
shopt -s histverify    Don’t execute expanded result immediately
Expansions

The History library provides a history expansion feature that makes easy to repeat commands, insert the arguments to a previous command into the current input line, or fix errors in previous commands quickly.

!$         Expand last parameter of most recent command
!*         Expand all parameters of most recent command
!-n        Expand nth most recent command
!n         Expand nth command in history
!<command> Expand most recent invocation of command <command>

and

!!:s/<FROM>/<TO>/       Replace first occurrence of <FROM> to <TO> in most recent command
!!:gs/<FROM>/<TO>/      Replace all occurrences of <FROM> to <TO> in most recent command
!$:t                    Expand only basename from last parameter of most recent command
!$:h                    Expand only directory from last parameter of most recent command

!! and !$ can be replaced with any valid expansion.

and finally

!!:n              Expand only nth token from most recent command (command is 0; first param is 1)
!!:n-m            Expand range of tokens from most recent command
!!:n-$            Expand the token to last from most recent command

!! can be replaced with any valid expansion i.e. !cat, !-2, !42, etc.

Some miscellaneous

Numeric calculations
$((a + 200))         # Add 200 to $a

$((RANDOM%=200))     # Random number 0..200
Redirection
python hello.py > output.txt      # stdout to (file)
python hello.py >> output.txt     # stdout to (file), append
python hello.py 2> error.log      # stderr to (file)
python hello.py 2>&1              # stderr to stdout
python hello.py 2>/dev/null       # stderr to (null)
python hello.py &>/dev/null       # stdout and stderr to (null)
Inspecting commands
command -V cd      #=> "cd is a shell builtin"
Source relative
source "${0%/*}/../share/foo.sh"
Directory of script
DIR="${0%/*}"
Getting options
while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in 
  -V | --version )
    echo $version
    exit
  ;;
  -s | --string )
    shift; string=$1
  ;;
  -f | --flag )
    flag=1
  ;;
esac; shift; done

if [[ "$1" == '--' ]]; then shift; fi
Heredoc

A block of code or text which can be redirected to the command script or interactive program is called here document or HereDoc.

cat <<END
hello world
END
Reading input
echo -n "Proceed? [y/n]: "
read ans
echo $ans
read -n 1 ans # Just one character
Special variables
$?     Exit status of last task
$!     PID of last background task
$$     PID of shell

 


References and further readings