This post is more or less a Bash syntax reference guide and isn’t intended to be detailed user guide for learning how to Bash script. It does however, include most of the syntax you’ll need to create shell scripts.

Much of this content is borrowed from other authors and open-source content creators. I’ve simply combined their works into a easy to follow format. I’ll do my best to credit the original author and provide sources whenever possible.

Example Script

#!/usr/bin/env bash

NAME="Robert"
echo "Hello $NAME!"

Variables

NAME="Robert"
echo $NAME        # Robert
echo "$NAME"      # Robert
echo "${NAME}!"   # Robert!

String Quotes

NAME="Robert"
echo "Hello $NAME"    # Hello Robert
echo 'Hello $NAME'    # Hello $NAME

Command Substitution

echo "The current directory is $(pwd)"

See command substitution for more information.

Conditional Execution

git commit && git push
git commit || echo "Commit Failed!"

Functions

display_name() {
  NAME="Robert"
  echo "$NAME"
}

echo "Hello, My name is $(display_name)"

Conditionals

if [ -z "$variable" ]; then
  echo "Variable is empty"
elif [ -n "$variable" ]; then
  echo "Variable is not empty"
fi

Strict Mode

set -euo pipefail
IFS=$'\n\t'

See unofficial bash strict mode for more information.

Brace Expansion

echo {A,B}.sh
ExpressionDescription
{A,B}Same as A B
{A,B}.shSame as A.sh B.sh
{1..5}Same as 1 2 3 4 5

See brace expansion for more information.

Parameter Expansions

Basics

name="Robert"
echo ${name}          # "Robert"
echo ${name/R/r}      # "robert" (substitution)
echo ${name:0:2}      # "Ro" (slicing)
echo ${name::2}       # "Ro" (slicing)
echo ${name::-1}      # "Rob" (slicing)
echo ${name:(-1)}     # "t" (slicing from right)
echo ${name:(-2):1}   # "r" (slicing from right)
length=2
echo ${name:0:length}   # "Ro"

See parameter expansion for more information.

STR="/path/to/foo.cpp"
echo ${STR%.cpp}      # /path/to/foo
echo ${STR%.cpp}.o    # /path/to/foo.o
echo ${STR%/*}        # /path/to

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}   # "orld!"
SRC="/path/to/foo.cpp"
BASE=${SRC##*/}     # "foo.cpp" (basepath)
DIR=${SRC%$BASE}    # "/path/to/" (dirpath)

Substitution

CodeDescription
${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

Comments

# This is a single line comment!
# This is a
# multi-line
# comment
: '
This is a
multi-line
comment
'

Substrings

ExpressionDescription
${FOO:0:3}Substring (position, length)
${FOO:(-3):3}Substring from the right

Length

ExpressionDescription
${#FOO}Length of $FOO

Manipulation

STR="HELLO WORLD!"
echo ${STR,}    # hELLO WORLD! (first letter lowercase)
echo ${STR,,}   # hello world! (all lowercase)

STR="hello world!"
echo ${STR^}    # Hello world! (first letter uppercase)
echo ${STR^^}   # HELLO WORLD! (all uppercase)

Default Values

ExpressionDescription
${FOO:-val}$FOO, or val if unset (or null)
${FOO:=val}Set $FOO to val if unset (or null)
${FOO:+val}val if $FOO is set (and not null)
${FOO:?message}Show error message and exit if $FOO is unset (or null)

Omitting the : removes the (non)nullity checks. ${FOO-val} expands to val if unset otherwise $FOO.

Loops

Basic For Loop

for i in /etc/rc.*; do
  echo $i
done

C-style For Loop

for ((i = 0 ; i < 100 ; i++)); do
  echo $i
done

Ranges

for i in {1..5}; do
  echo "Welcome $i"
done

Step Size

for i in {5..50..5}; do
  echo "Welcome $i"
done

Reading Lines

cat file.txt | while read line; do
  echo $line
done

Forever

Be careful when using infinite loops! If, not used properly may crash a running system.

while true; do
  ···
done

Functions

Defining functions

myfunc() {
  echo "Hello $1"
}

This function is the same as above, but with alternate syntax.

function myfunc() {
  echo "Hello $1"
}
myfunc "Robert"

Returning Values

myfunc() {
  local myresult='some value'
  echo $myresult
}
result="$(myfunc)"

Raising Errors

myfunc() {
  return 1
}
if myfunc; then
  echo "success"
else
  echo "failure"
fi

Arguments

ExpressionDescription
$#Number of arguments
$*All arguments
$@All arguments, starting from first
$1First argument
$_Last argument of the previous command

See special parameters for more information.

Conditionals

Conditions

Using [[ is actually a command-line program that returns either 0 (true) or 1 (false). Any program that obeys the same logic can be used as conditionals. All base utilities, such as grep or ping support conditionals.

ConditionDescription
[[ -z STRING ]]Empty string
[[ -n STRING ]]Not empty string
[[ STRING == STRING ]]Equal
[[ STRING != STRING ]]Not Equal
[[ 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

More Conditions

ConditionDescription
[[ -o noclobber ]]If OPTIONNAME is enabled
[[ ! EXPR ]]Not
[[ X && Y ]]And
[[ X || Y ]]Or

File Conditions

ConditionDescription
[[ -e FILE ]]Exists
[[ -r FILE ]]Readable
[[ -h FILE ]]Symbolic Link
[[ -d FILE ]]Directory
[[ -w FILE ]]Writable
[[ -s FILE ]]Size > 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

Examples

# String
if [[ -z "$string" ]]; then
  echo "String is empty"
elif [[ -n "$string" ]]; then
  echo "String is not empty"
else
  echo "This never happens"
fi
# Combinations
if [[ X && Y ]]; then
  ...
fi
# Equal
if [[ "$A" == "$B" ]]
# Regex
if [[ "A" =~ . ]]
if (( $a < $b )); then
  echo "$a is smaller than $b"
fi
if [[ -e "file.txt" ]]; then
  echo "file exists"
fi

Arrays

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[-1]}      # Last element
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)
echo ${!Fruits[@]}      # Keys of all elements, space-separated

Operations

Fruits=("${Fruits[@]}" "Watermelon")      # Push
Fruits+=('Watermelon')                    # Also 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

Dictionaries

Defining

declare -A sounds
sounds[dog]="bark"
sounds[cow]="moo"
sounds[bird]="tweet"
sounds[wolf]="howl"

Declares sound as a dictionary object (associative array).

Working with Dictionaries

echo ${sounds[dog]}   # Dog's sound
echo ${sounds[@]}     # All values
echo ${!sounds[@]}    # All keys
echo ${#sounds[@]}    # Number of elements
unset sounds[dog]     # Delete dog

Iteration

Iterate over Values

for val in "${sounds[@]}"; do
  echo $val
done

Iterate over Keys

for key in "${!sounds[@]}"; do
  echo $key
done

Options

Basic Options

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

Glob Options

shopt -s nullglob     # Non-matching globs are removed  ('*.foo' => '')
shopt -s failglob     # Non-matching globs throw errors
shopt -s nocaseglob   # Case insensitive globs
shopt -s dotglob      # Wildcards match dotfiles ("*.sh" => ".foo.sh")
shopt -s globstar     # Allow ** for recursive matches ('lib/**/*.rb' => 'lib/a/b/c.rb')

Set GLOBIGNORE as a colon-separated list of patterns to be removed from glob matches.

History

Commands

CommandDescription
historyShow history
shopt -s histverifyDon’t execute expanded result immediately

Expansions

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

Operations

CodeDescription
!!Execute last command again
!!: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
!$:tExpand only basename from last parameter of most recent command
!$:hExpand only directory from last parameter of most recent command

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

Slices

CodeDescription
!!:nExpand only nth token from most recent command (command is 0; first argument is 1)
!^Expand first argument from most recent command
!$Expand last token from most recent command
!!:n-mExpand range of tokens from most recent command
!!:n-$Expand nth token to last from most recent command

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

Miscellaneous

Numeric Calculations

$((a + 200))    # Add 200 to $a
$(($RANDOM%200))    # Random number 0..199

Sub-Shells

(cd /var/tmp; echo "I'm now in $PWD")
pwd   # still in the original directory

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)
python hello.py < foo.txt   # feed foo.txt to stdin for python

Inspecting Commands

command -V cd   # "cd is a function/alias/whatever"

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

Case and Switch

case "$1" in
  start | up)
    vagrant up
    ;;
  *)
    echo "Usage: $0 {start|stop|ssh}"
    ;;
esac

Source Relative

source "${0%/*}/../share/foo.sh"

Format and Print Data

Use the printf command to format and print data.

printf "Hello %s, I'm %s" Sven Olga     # "Hello Sven, I'm Olga
printf "1 + 1 = %d" 2                   # "1 + 1 = 2"
printf "How you print a float: %f" 2    # "This is how you print a float: 2.000000"

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

cat <<EOF_XYZ
hello world
EOF_XYZ
cat <<-EOF_XYZ
  hello world
  EOF_XYZ

Reading User Input

echo -n "Proceed? [y/n]: "
read ans
echo $ans
read -n 1 ans   # Just one character

Special Variables

ExpressionDescription
$?Exit status of last task
$!PID of last background task
$$PID of shell
$0Filename of the shell script

See special parameters for more information.

Goto Previous Directory

pwd   # /home/user/foo
cd bar/
pwd   # /home/user/foo/bar
cd -
pwd   # /home/user/foo

Check for Command Result

if ping -c 1 www.laroccx.com; then
  echo "You're connected to the internet!"
else
  echo "You're not connected to the internet!"
fi

Sources