Bash

The Bourne Again Shell, bash, is a standard command line interpreter that is POSIX compliant.

Hello World

echo "Hello, World!"

Redirection and Substitution

SymbolDescriptionExample
``Pipe STDOUT from one command to STDIN of another
>Redirect STDOUTls > output.txt
>>Redirect STDOUT and append to filels / >> output.txt
2>Redirect STDERRls 2> output-err.txt
2>>Redirect STDERR and append to filels / 2>> output-err.txt
<Redirect file as STDINecho 'hello world' > a; grep 'hello' < a
<<EOFRead in text until EOF is encountered and send as STDINgrep 'hello' <<EOF\nhello world\nEOF
<<-EOFSame as <<EOF with leading tabs removedgrep 'hello' <<EOF\n\thello world\nEOF
<<<Redirect string as STDINgrep 'hello' <<< 'hello world'
>()Receive STDIN to command as a fileecho 'hello world' > >(grep 'hello')
<()Send STDOUT of command as a filegrep 'hello' <(echo 'hello world')
$()Use output of command as stringecho '/tmp' > a; ls $(cat a)
$(())Use output of arithmetic as stringecho "1+1 is $((1+1))"

Control Operators

SymbolDescription
[ testCondition evaluation utility command
if elif elseEvaluate statement if the return code is 0
[[ ]]Shell built-in to enhance [
forIterate over a list of values
whileDo commands while command returns 0
caseSelectively execute commands based on matching pattern
selectContinuously ask for user to select a choice from a list and execute commands

Single vs Double Bracket

[ or test is a command whereas [[ is a shell built-in. I prefer [[ over [ because it offers better regex and boolean operations.

[ syntax

FlagDescription
-zTrue if the length of the string is zero
-nTrue if the length of the string is non zero
-eTrue if the file exists
-fTrue if the file exists and is a regular file
-dTrue if the file exists and is a directory
-rTrue if the file exists and is readable
-wTrue if the file exists is writeable
-xTrue if the file exists is executable
-sTrue if the file exists and has a size greater than zero
-LTrue if the file exists and is a symbolic link
-OTrue if the file exists and its owner matches the effective user id of this process
-tTrue if the file descriptor is opened on a terminal
-ntTrue if file1 is newer than file2
-otTrue if file1 is older than file2
-efTrue if file1 and file2 refer to the same file

[[ syntax

[[ builds upon [ syntax.

FlagDescription
-NTrue if file exists and has been modified since it was last read
-oTrue if shell option is enabled

Examples

# if syntax
if [[ $variable == pattern* ]]; then
    echo 'if'
elif [[ $variable == "const" ]]; then
    echo 'elif'
else
    echo 'else'
fi

# iterate over list
for variable in list of words; do
    echo $variable
done

# standard for loop
for (( i=0; i < 10; i++ )); do
    echo $i
done

# while
while true; do
    echo 'infinte loop?'
    break
done

# case
case $1 in
    -g|--greet)
        echo 'Hello!'
    ;;
    -h|--help)
        echo 'This is a case'
    ;;
    *) echo 'Invalid argument' ;;
esac

# select
select var in one two three; do
    echo "You selected: $var"
done

Parameter Substitution

Bash has built-in variable substitution and manipulation.

# set default if variable is unset
${variable=default}

# set default if variable is empty or unset
${variable:=default}
${variable:-default}
${variable-default}

# set value if variable is set
${variable+value}
${variable:+value}

# use variable or abort and print err_msg if unset
${variable?err_msg}
${variable:?err_msg}

# non-greedily remove the pattern that matches the prefix
${var#pattern}
# greedily remove the pattern that matches the prefix
${var##pattern}

# non-greedily remove the pattern that matches the suffix
${var%pattern}
# greedily remove the pattern that matches the suffix
${var%%pattern}

# substring var[pos:pos+len]
${var:pos:len}

# search and replace pattern with replacement
${var/pattern/replacement}

# global search and replace pattern with replacement
${var//pattern/replacement}

# search and replace the prefix of var
${var/#pattern/replacement}

# search and replace the suffix of var
${var/%pattern/replacement}

Reference: Advanced Bash-Scripting Guide: Manipulating Variables

Arguments

Functions and scripts have access to arguments through $1, $2, $3 (and so on) environment variables. There are many other useful ways to access the arguments.

The following table will refer to this example:

./test.sh a b c
ExpressionDescriptionExample Output
$0The name of the shell script./test.sh
$1The first positional argumenta
$2The second positional argumentb
$3The third positional argumentc
$@All positional arguments starting from 1 as an arraya b c
${@: -1}The last positional argumentc
${@: -2}The last 2 positional argumentsb c
${@: 2: 1}1 positional argument starting at position 2b

Special Variables

VariableDescription
$#Number of positional arguments
$*All positional arguments as a single string
$?Exit status of most recently executed foreground pipeline
$$Process ID of the shell
$!Process ID of the job most recently placed in the background
$_Last argument to the previous simple foreground command

Reference: GNU Manual: Bash Special Parameters

Arrays

There are two types of bash arrays: indexed and associative.

An indexed array is created automatically if any variable is assigned to using the syntax name[subscript]=value. Alternatively, use declare:

declare -a var1 # creates an indexed array
declare -A var2 # creates an associative array

Assigning to an array follows the same pattern for both indexed and associative arrays. Negative values in indexed arrays will be the offset from the last element.

var1=(val1 val2 val3)
var2=(key1 val1 key2 val2 key3 val3)

# alternative syntax
var1=([0]=val1 [1]=val2 [2]=val3)
var2=([key1]=val1 [key2]=val2 [key3]=val3)

# single assignment
var1[-1]=val4
var2[key4]=val4

Arrays may be accessed using their respective index (or key). @ or * may be used to access all values.

# get value
echo "${var1[2]}"
echo "${var2[key3]}"

# get all keys
echo "${!var1[@]}"
echo "${!var2[@]}"

Example

# uncomment to make associative
# declare -A arr
arr=("hello world" foo bar baz)

for k in "${!arr[@]}"; do
    v="${arr[$k]}"
    echo "arr[$k] = $v"
done

Reference: GNU Manual: Bash Arrays

Option Parsing

There are two ways to parse command line options in bash: getopt and getopts. getopts is a bash built-in while getopt is a command. I prefer using the enhanced getopt as it allows long options, however the enhanced version is not on macOS by default. For fun and portability, I wrote my own version in pure bash.

Trap

Bash allows to trap signals and other events using the trap command.

trap "echo INT received" SIGINT
SignalDescription
EXITExecuted on exit from the shell
DEBUGExecuted before every simple command
RETURNExecuted each time a shell function or script run by . or source builtins finishes executing
ERRExecuted each time a command’s failure would cause the shell to exit (when -e option is enabled)
SIGINTInterrupt signal
SIGTERMTerminate signal

See man 7 signal for a complete list of signals to trap.

Common Operations

Read every line of a file

while read line; do
    echo $line
done < file.txt

Read input and continue / abort

read -p 'Continue? [y/N]: ' i
if [[ ${i::1} != 'y' && ${i::1} != 'Y' ]]; then
    echo 'Aborting'
    exit 1
fi

Generate tempfile / cleanup

tmpfile=$(mktemp)
trap "rm $tmpfile" EXIT

Generate a random string of alphanumeric characters

cat /dev/urandom | tr -cd 'a-zA-Z0-9' | head -c 64

Use vi keybindings in interactive sessions

set -o vi

Get the script directory

script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)

Source