Getting started

Introduction

This is a quick reference to getting started with Bash scripting.

Example

#!/usr/bin/env bash

name="John"
echo "Hello $name!"

See: Shell scripts

Variables

name="John"
echo $name  # see below
echo "$name"
echo "${name}!"

Generally quote your variables unless they contain wildcards to expand or command fragments.

wildcard="*.txt"
options="iv"
cp -$options $wildcard /tmp

See: Shell parameters

String quotes

name="John"
echo "Hi $name"  #=> Hi John
echo 'Hi $name'  #=> Hi $name
echo $'Hi\nJohn'  # Hi, then John

See: Quoting

Shell execution

echo "I'm in $(pwd)"
echo "I'm in `pwd`"  # deprecated
echo "$(< file.txt)"  # no fork

See: Command substitution

Conditional execution

git commit && git push
git commit || echo "Commit failed"

See: Lists

Functions

get_name() {
  echo "John"
}

echo "You are $(get_name)"

See: Functions

Conditionals

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

See: Conditionals

Strict mode

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

errexit ignores failures in conditions, all but the final command of &&/|| lists, and ! pipelines.

See: The set builtin

Brace expansion

echo {A,B}.js
ExpressionDescription
{A,B}Same as A B
{A,B}.jsSame as A.js B.js
{1..5}Same as 1 2 3 4 5
{{1..3},{7..9}}Same as 1 2 3 7 8 9

See: Brace expansion

Parameter expansions

Basics

name="John"
echo "${name}"        # John
echo "${name/J/j}"    # john (replace)
echo "${name:0:2}"    # Jo (slice)
echo "${name::2}"     # Jo (slice)
echo "${name::-1}"    # Joh (drop last)
echo "${name:(-1)}"   # n (from right)
echo "${name:(-2):1}" # h (from right)
echo "${food:-Cake}"  # food, or Cake

A negative offset needs a space or parentheses: ${name: -1}.

length=2
echo "${name:0:length}"  # Jo
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 (basename)

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=${src##*/}   # foo.cpp (basename)
dir=${src%$base}  # /path/to/ (dirname)
dir=${src%/*}     # /path/to (dirname)

See: Parameter expansion

Prefix name expansion

prefix_a=one
prefix_b=two
echo "${!prefix_*}"  # prefix_a prefix_b

See: Shell parameter expansion

Indirection

name=joe
pointer=name
echo ${!pointer}
joe

See: Shell parameter expansion

Substitution

CodeDescription
${foo%suffix}Remove shortest suffix
${foo#prefix}Remove shortest prefix
${foo%%suffix}Remove longest suffix
${foo/%suffix}Remove suffix (anchored)
${foo##prefix}Remove longest prefix
${foo/#prefix}Remove prefix (anchored)
${foo/from/to}Replace first match
${foo//from/to}Replace all
${foo/%from/to}Replace suffix
${foo/#from/to}Replace prefix

See: Shell parameter expansion

Comments

# Single line comment
: '
This is a
multi line
comment
'

See: Comments

Substrings

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

See: Shell parameter expansion

Length

ExpressionDescription
${#foo}Length of $foo

See: Shell parameter expansion

Manipulation

str="HELLO WORLD!"
echo "${str,}"   # hELLO WORLD!
echo "${str,,}"  # hello world!

str="hello world!"
echo "${str^}"   # Hello world!
echo "${str^^}"  # HELLO WORLD!

See: Shell parameter expansion

Transformations

ExpressionDescription
"${foo@Q}"Quoted, reusable as input
"${foo@U}"Uppercase
"${foo@L}"Lowercase
"${foo@u}"Uppercase first character
"${foo@E}"Expand $'…' escapes
"${foo@P}"Expand as a prompt string
"${foo@A}"Assignment or declare form
"${foo@a}"Attribute flags
"${foo[@]@K}"Quoted key-value pairs
"${foo[@]@k}"Key-value pairs, split

See: Shell parameter expansion

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}Error if $foo is unset (or null)

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

See: Shell parameter expansion

Loops

Basic for loop

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

See: Looping constructs

C-like for loop

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

See: Looping constructs

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

See: Brace expansion

Reading lines

while IFS= read -r line; do
  echo "$line"
done <file.txt
mapfile -t lines < file.txt

read in a pipeline runs in a subshell. shopt -s lastpipe (plus set +m interactively) keeps the last stage in the current shell.

See: read, mapfile

Forever

while true; do
  ···
done

See: Looping constructs

Functions

Defining functions

myfunc() {
    echo "hello $1"
}
# Same as above (alternate syntax)
function myfunc {
    echo "hello $1"
}
myfunc "John"

See: Shell functions

Returning values

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

See: Command substitution

Raising errors

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

See: Exit status

Arguments

ExpressionDescription
$#Number of arguments
$*All positional arguments (as a single word)
$@All positional arguments (as separate strings)
$1First argument
$_Last argument of the previous command

Note: $@ and $* must be quoted in order to perform as described. Otherwise, they do exactly the same thing (arguments as separate strings).

See Special parameters.

Conditionals

Conditions

[[ is a shell keyword returning 0 (true) or 1 (false); any command with that exit-status logic works too.

ConditionDescription
[[ -z STRING ]]Empty string
[[ -n STRING ]]Not empty string
[[ STRING == STRING ]]Equal (glob pattern)
[[ STRING != STRING ]]Not equal (glob)
[[ 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 match
(( NUM < NUM ))Arithmetic comparison

More conditions

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

See: Bash conditional expressions

File conditions

ConditionDescription
[[ -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 file (inode)

See: Bash conditional expressions

Example

# 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 [[ -n "$X" && -n "$Y" ]]; then
  echo "Both are set"
fi
# Equal
if [[ "$A" == "$B" ]]
# Regex
if [[ "$date" =~ ^([0-9]{4})- ]]; then
  echo "${BASH_REMATCH[1]}"
fi

BASH_REMATCH holds the whole match, then each capture group.

if (( a < b )); then
   echo "$a is smaller than $b"
fi
if [[ -e "file.txt" ]]; then
  echo "file exists"
fi

See: Conditional constructs

Arrays

Defining arrays

Fruits=('Apple' 'Banana' 'Orange')
Fruits[0]="Apple"
Fruits[1]="Banana"
Fruits[2]="Orange"

See: Arrays

Working with arrays

echo "${Fruits[0]}"      # Element #0
echo "${Fruits[-1]}"     # Last element
echo "${Fruits[@]}"      # All elements
echo "${#Fruits[@]}"     # Number of elements
echo "${#Fruits}"        # Length of the 1st element
echo "${#Fruits[3]}"     # Length of the Nth element
echo "${Fruits[@]:3:2}"  # From position 3, length 2
echo "${!Fruits[@]}"     # Indices

See: Arrays

Operations

Fruits=("${Fruits[@]}" "Watermelon")    # Push
Fruits+=('Watermelon')                  # Also push
Fruits=("${Fruits[@]/Ap*/}")            # Blank matching elements
unset 'Fruits[2]'                       # Remove one item
Fruits=("${Fruits[@]}")                 # Duplicate
Fruits=("${Fruits[@]}" "${Veggies[@]}") # Concatenate
mapfile -t lines < datafile             # One line per element

See: Arrays

Iteration

for i in "${arrayName[@]}"; do
  echo "$i"
done

See: Arrays

Dictionaries

Defining

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

declare -A creates an associative array (bash 4+).

See: Arrays

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

See: Arrays

Iteration

Iterate over values

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

Iterate over keys

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

See: Arrays

Options

Options

set -o noclobber  # Prevent > from overwriting files
set -o errexit    # Exit on error
set -o pipefail   # Fail if any pipe stage fails
set -o nounset    # Error on unset variables

See: The set builtin

Glob options

shopt -s nullglob    # Non-matching globs are removed
shopt -s failglob    # Non-matching globs throw errors
shopt -s nocaseglob  # Case insensitive globs
shopt -s dotglob     # Wildcards match dotfiles
shopt -s globstar    # ** matches recursively
shopt -s extglob     # Extended patterns: @(a|b), !(a)

globskipdots is on by default since bash 5.2. GLOBSORT sets sort order (name, size, mtime).

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

See: The shopt builtin

History

Commands

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

See: Bash history facilities

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>

See: Event designators

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.

See: Modifiers

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.

See: Word designators

Miscellaneous

Numeric calculations

echo $((a + 200))      # Add 200 to $a
echo $((RANDOM % 200))  # Random number 0..199
declare -i count  # Declare as type integer
count+=1          # Increment

See: Shell arithmetic

Subshells

(cd somedir; echo "I'm now in $PWD")
pwd # still in first directory

See: Command grouping

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 >output.txt 2>&1 # both, same as &>
python hello.py &>/dev/null      # both to null
echo "$0: warning: too many users" >&2  # diagnostic
python hello.py < foo.txt      # feed foo.txt to stdin for python
diff <(ls -r) <(ls)            # Compare two stdout without files

See: Redirections

Inspecting commands

command -V cd
#=> cd is a shell builtin

See: Bash builtins

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

See: The trap builtin

Case/switch

case "$1" in
  start | up)
    vagrant up
    ;;

  *)
    echo "Usage: $0 {start|stop|ssh}"
    ;;
esac

See: Conditional constructs

Source relative

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

BASH_SOURCE points at the file even when the script is sourced.

See: BASH_SOURCE

printf

printf "Hello %s, I'm %s" Sven Olga
#=> Hello Sven, I'm Olga

printf "1 + 1 = %d" 2
#=> "1 + 1 = 2"

printf "This is how you print a float: %f" 2
#=> "This is how you print a float: 2.000000"

printf '%s\n' '#!/bin/bash' 'echo hello' >file
# format string is applied to each group of arguments
printf '%i+%i=%i\n' 1 2 3  4 5 9

See: The printf builtin

Transform strings

Command optionDescription
-cOperations apply to characters not in the given set
-dDelete characters
-sReplaces repeated characters with single occurrence
-tTruncates
[:upper:]All upper case letters
[:lower:]All lower case letters
[:digit:]All digits
[:space:]All whitespace
[:alpha:]All letters
[:alnum:]All letters and digits

Example

echo "Welcome To Devhints" | tr '[:lower:]' '[:upper:]'
WELCOME TO DEVHINTS

See: tr invocation

Directory of script

dir=${BASH_SOURCE[0]%/*}

See: BASH_SOURCE

Getting options

Short options

while getopts ":Vs:f" opt; do
  case $opt in
    V) echo "$version"; exit ;;
    s) string=$OPTARG ;;
    f) flag=1 ;;
    :) echo "Missing argument for -$OPTARG" >&2; exit 1 ;;
    *) echo "Usage: $0 [-V] [-s string] [-f]"; exit 1 ;;
  esac
done
shift $((OPTIND - 1))

OPTARG holds the option value; OPTIND is the next argument index.

Long 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

See: getopts

Heredoc

cat <<EOF
hello world
EOF

Heredoc feeds multiple lines to stdin. Unquoted markers expand variables; 'EOF' disables expansion.

See: Here documents

Herestring

tr '[:lower:]' '[:upper:]' <<< "Will be uppercased, even $variable"

Herestring allows a string to be treated as a standard input (stdin). See Bash Reference Manual.

Process substitution

# loop on myfunc output lines
while IFS= read -r line; do
  echo "$line"
done < <(myfunc)

# compare content of two folders
diff <(ls "$dir1") <(ls "$dir2")

Process substitution treats a command's input or output as a file.

See: Process substitution

Reading input

read -r -p "Proceed? [y/n] " ans
IFS= read -s -r -p "Password: " pass  # no echo
echo "$ans"

-r keeps backslashes literal, -p prints a prompt, and -s hides input.

read -n 1 ans    # One character

See: The read builtin

Special variables

ExpressionDescription
$?Exit status of last task
$!PID of last background task
$$PID of shell
$0Name of the shell or script
$_Last argument of the previous command
${PIPESTATUS[n]}Exit status of piped commands (array)
$EPOCHSECONDSSeconds since the Unix epoch
$SRANDOM32-bit random number

See Special parameters.

Go to previous directory

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

See: The cd builtin

Check for command's result

if ping -c 1 google.com; then
  echo "It appears you have a working internet connection"
fi

See: Exit status

Grep check

if grep -q 'foo' ~/.bash_history; then
  echo "You appear to have typed 'foo' in the past"
fi

See: GNU grep manual

Also see

0 Comments for this cheatsheet. Write yours!