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