home blog portfolio Ian Fisher

The Bash shell

The ubiquitous shell on Linux systems, warts and all. Not to be confused with /bin/sh, which is often a less-fully-featured shell.

Cheatsheet

Strings

${s%suffix} # remove suffix
${s#prefix} # remove prefix
${s/from/to} # replace first occurrence
${s//from/to} # replace all occurrences
${s:-whatever} # take value of $s or 'whatever' if unset

${x:-default} # variable or default value

Redirection

# redirect stdout to stderr
echo hello >&2

# redirect both stdout and stderr
echo hello &>log.txt

Order matters! echo hello >log.txt 2>&1 redirects both standard output and standard error to the file log.txt. Think of the redirection operator as an assignment.

Heredocs

cat << EOF
Line 1
Line 2
EOF

cat << "EOF"
No parameter expansion is done.
EOF

    cat <<- EOF
    The <<- operator lets you indent your code.
    EOF

s=$(cat << EOF
Line 1
Line 2
EOF
)

Control flow

for p in *.csv; do
  # ...
done

if [[ ... ]]; then
fi

case "$x" in
  pattern)
    ;;
  *)
    ;;
esac

Conditions

[[ "$s" == "prefix"* ]] # string has prefix
[[ -z "$s" ]] # string is empty
[[ -n "$s" ]] # string is not empty

[[ -e "$f" ]] # file exists
[[ -L "$f" ]] # symlink exists (regardless of whether target exists)
[[ -d "$f" ]] # file is directory
[[ -f "$f" ]] # file is regular file
[[ -h "$f" ]] # file is a symlink
[[ -x "$f" ]] # file is executable

(( x == 100 )) # no needs for '$'

Arrays

arr=()
arr+=("$x")
${#arr[@]} # array length
${arr[@]:1} # slice starting from first element
for x in "${arr[@]}"; do .. done

Associative arrays

declare -A mymap
mymap["key"]="value"
echo "${mymap[key]}"
unset mymap["key"]

# check if a key is set (even if empty)
# NOTE that it's `mymap["key"]`, not `${mymap["key"]}`
[[ -v mymap["key"] ]] && echo exists

# iterate over keys
for key in "${!mymap[@]}"; do
  # ...
done

# iterate over values
for key in "${mymap[@]}"; do
  # ...
done

# pre-declare and make read-only (-r)
declare -A -r mymap=(
  ["key1"]="value1"
  ["key2"]="value2"
)

Temporary files and directories

tmp="$(mktemp)"
tmpdir="$(mktemp -d)"

# with specific extension
# https://stackoverflow.com/a/59638023/3934904
tmpdir="$(mktemp -d)"
tmp="$tmpdir/test.md"

Clean-up functions

cleanup() { ... }
trap cleanup EXIT

Get script path

SCRIPT_PATH="$(realpath "$0")"

Temporarily allow unset variables

if [[ $- = *u* ]]; then
  restore_unset="1"
else
  restore_unset="0"
fi
set +u

# ...

if (( restore_unset == 1 )); then
  set -u
fi

tar

# create a gzipped archive of a directory with relative paths
# NOTE: '.' at the end
tar -czf OUTFILE --directory=DIR .

# exclude a directory
# NOTE: exclude path is relative to DIR, not the current working directory
tar -czf OUTFILE --exclude=EXCLUDE --directory=DIR .

# extract tarball
tar -xzf TARBALL -C DIR

Tips

Be careful

Posts

Bibliography

Reference:

Writing robust scripts:

Minutiae: