Bash scripting: my own cheatsheet

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.


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

echo "Hello $NAME!"

Some more examples:

echo $NAME
echo "$NAME"
echo "${NAME}!"
String quotes
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


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.

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"
echo ${name:0:length} #=> "An"
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"
BASE=${STR##*/}          #=> "foo.cpp" (basepath)
DIR=${SRC%$BASE}         #=> "/path/to" (dirpath)
${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 "Hello %s, I'm %s: my bowl is empty!" Andrea Ivo #=> "Hello Andrea, I'm Ivo: my bowl is empty!


# Single line comment

: '
This is a
multi line

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


${FOO:0:3}   Substring (position, length)
${FOO:-3:3}  Substring from the right
${#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


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
for i in {1..5}; do
  echo "Welcome $i"

With step size

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


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

Raise errors
myfunc() {
  return 1

if myfunc; then
  echo "success"
  echo "failure"
$#   Number of arguments
$*   All arguments
[email protected]   All arguments, starting from first
$1   First argument
Trap errors
trap 'echo Error at about $LINENO' ERR


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


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 "$1" in
  start | up)
    vagrant up

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

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

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

if (( $a < $b ))

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


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')

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)
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
for i in "${arrayName[@]}"; do
   echo $i


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                Show history
shopt -s histverify    Don’t execute expanded result immediately

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>


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

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

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

1 Reply to “Bash scripting: my own cheatsheet”

  1. This is a GREAT reference. Learned at least 10 things I did not know before. Hope you do not mind I archived a copy for my own reference later in case you take this site down.


This site uses Akismet to reduce spam. Learn how your comment data is processed.