Skip to main content
  1. Knowledge Base/
  2. Programming/

Bash

Table of Contents

Shebang
#

#!/bin/bash
#!/usr/bin/env bash # suitable for NixOS!

Strict
#

set -euo pipefail

Debugging
#

bash -x script
set -x # start debugging from here
echo Hello World!
set +x # stop debugging from here
set -v # print shell inputs as they are read
echo Hello World!
set +v # stops showing shell inputs
#!/bin/bash -xv # combining options for whole scripts in the shebang

Loops
#

for i in $( ls ); do
	echo item: $i
done
for i in `seq 1 10`;
	do
		echo $i
done
COUNTER=0
while [  $COUNTER -lt 10 ]; do
   	echo The counter is $COUNTER
       	let COUNTER=COUNTER+1
done
COUNTER=20
until [  $COUNTER -lt 10 ]; do
	echo COUNTER $COUNTER
    	let COUNTER-=1
done

IF Condition
#

One-liner
#

  [[ -f $tmpfile ]] || rm $tmpfile

Env
#

if [ "$ENV_VAR" = "true" ] ; then
	echo $ENV_VAR
fi

Files
#

[ -a FILE ] # True if file exists
[ -d FILE ] # True if file exists and is a directory
[ -f FILE ] # True if file exists and is a regular file
[ -h FILE ] # True if file exists and is a symbolic link
[ -s FILE ] # True if file exists and its size is greater than 0
[ -rwx FILE ] # True if file exists and is readable, writable, executable
[ FILE1 -nt FILE2 ] # True if FILE1 has been changed more recently or if FILE1 exists and FILE2 does not
[ FILE1 -ot FILE2 ] # True if FILE1 is older or FILE1 exists and FILE2 does not

Strings
#

[ -z "STRING" ] # True if length of STRING is zero
[ "STRING1" != "STRING2" ] # True if strings are (not) equal

String in file
#

if grep -q <PATTERN> <FILE>; then
  echo "True"
else
  echo "FALSE"
fi

Integers
#

[ NUM1 -eq NUM2 ] # True if NUM1 is equal to NUM2
[ NUM1 -ne NUM2 ] # True if NUM1 is not equal to NUM2
[ NUM1 -gt NUM2 ] # True if NUM1 is greater than NUM2
[ NUM1 -ge NUM2 ] # True if NUM1 is greater or equal to NUM2
[ NUM1 -lt NUM2 ] # True if NUM1 is leass than NUM2
[ NUM1 -le NUM2 ] # True if NUM1 is less than or equal to NUM2
# Former statements work with double parentheses e.g. ((NUM1 <= NUM2))
[ NUM1 -lt NUM2 ]
[ NUM1 -lt NUM2 ]

Special variables
#

$# # number of arguments
$@ # array of all arguments
$? # last exit code

Case (switch) statement
#

case $1 in
    pattern1 )
        statements
        ;;
    pattern2 )
        statements
        ;;
    ...
esac

Combining Expressions
#

[ EXPR1 -a EXPR2 ] # True if both are true
[ EXPR1 -o EXPR2 ] # True if 1 or 2 is true

Create array from whitespace separated string
#

arr=($string)

Add up a list of numbers
#

<list of numbers> | paste -sd+ - | bc

Checks
#

Website
#

	while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost/grafana/login)" != "200" ]]; do sleep 5; done

Program
#

command -v PROGRAM >/dev/null 2>&1 || { echo >&2 "require foo"; exit 1; }
for program in awk sed grep sort uniq rm mktemp; do
  command -v "$program" > /dev/null 2>&1 || { echo "Not found: $program"; exit 1; }
done
checkUtils() {
    readonly MSG="not found. Please make sure this is installed and in PATH."
    readonly UTILS="awk basename cat column echo git grep head seq sort tput \
		tr uniq wc"

    for u in $UTILS
    do
        command -v "$u" >/dev/null 2>&1 || { echo >&2 "$u ${MSG}"; exit 1; }
    done
}

Process
#

pgrep -x <PROCESSNAME> >/dev/null && echo "Process found" || echo "Process not found"
ps -C <PROCESSNAME> >/dev/null && echo "Running" || echo "Not running"

User
#

getent passwd USER
id -u name

Root user
#

POSIX compliant

if ! [ $(id -u) = 0 ]; then
   echo "Must be run as root!"
   exit 1
fi

BASH

#!/bin/bash
if [[ $EUID -ne 0 ]]; then
   echo "Must be run as root!"
   exit 1
fi

Check if file is being sourced
#

Works for bash, ksh. zsh

([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] ||
 [[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" &&
    printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] ||
 [[ -n $BASH_VERSION && $0 != "$BASH_SOURCE" ]]) || { echo "This script should be sourced for convenience as it sets env variables in your parent shell!"; exit 1; }

PostgreSQL connection
#

apt-get install postgresql-client

while ! pg_isready -h ${HOST} -p ${PORT} &> /dev/null; do
	echo "Connection to ${HOST} ${PORT} failed "
	sleep 1
done

Argument count
#

  if [ $# -eq 0 ]; then
    echo "Missing argument(s)"
    echo "Usage: $(basename $0) foo"
    echo "Usage: $(basename $0) foo bar"
    exit 1
  fi

Git directory
#

if [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then
  echo "This is a git directory"
else
  echo "This is not a git directory"
fi

SED
#

Prepend text on multiple files
#

fd -e yaml | xargs sed -i '1s;^;TO-BE-PREPENDED;'
This command will edit all yaml files inplace! Backup!

Search / Replace
#

	sed -i \
	        -e "s;^\\(application-port\\)=.*;\\1=8080;g" \
	        -e "s;^\\(application-host\\)=.*;\\1=0.0.0.0;g" \
            -e "s/#\{0,1\}api_interface.*/api_interfaces:\"eth1\"/" \
	        /PATH/TO/FILE

capture groups
#

sed -i 's/httpCode:\([0-9]\+\)/StatusCode:\1/'

search and replace in multiple files
#

find . -type f -name 'config' | xargs sed -i -e  's/PATTERN/STRING/g'

Delete lines containing a pattern from multiple files
#

find . -type f -name '*.md' | xargs sed -i -e '/PATTERN/d'

AWK
#

Search and delete pattern
#

awk '{gsub(/search_pattern/,x); }'

Custom separator
#

awk -F= # = separator for e.g. columns

Column of line
#

free -h | awk '/Mem:/{print $2}'

Print all columns from nth column#

awk '{$1=$2=""; print $0}'

Remove first line
#

awk '{if (NR!=1) {print}}'

Split a string
#

echo -n "eins:zwei:drei | awk '{split($0,r,":"); print r[1]}';) # returns zwei

Sum up time durations
#

awk -F : '{acch+=$1;accm+=$2;} ENDFILE { print acch+int(accm/60)  ":" accm%60; }'

Get parent directory of a file
#

parent_dir="$(dirname -- "$(readlink -f -- "$file_name")")"

Time/Date
#

date --rfc-3339=seconds

Arrays
#

arr=(hello word array)
for i in ${arr[*]}; do
        echo "her is $i"
done

Iterate over a list of strings
#

LIST="crazymax/diun:4.20.1
grafana/grafana:8.3.2
k8s.gcr.io/kube-state-metrics/kube-state-metrics:v2.2.4
prom/blackbox-exporter:v0.19.0
prom/pushgateway:v1.4.1
quay.io/prometheus-operator/prometheus-config-reloader:v0.52.1
quay.io/prometheus-operator/prometheus-operator:v0.52.1
quay.io/prometheusmsteams/prometheus-msteams:v1.5.0"

while IFS= read -r line; do
	echo "$line" | awk -F':' '{print $2}'
done < <(printf '%s\n' "$LIST")

Will output:

4.20.1
8.3.2
v2.2.4
v0.19.0
v1.4.1
v0.52.1
v0.52.1
v1.5.0

Yes no choice selection
#

with known options

echo "Continue?"
select choice in "Yes" "No"; do
    case $choice in
        Yes ) echo "Going on; break;;
        No ) exit;;
    esac
done

with unknown options

array=("Option1" "Option2" "Option3")
select choice in "${array[@]}"; do
    [[ -n $choice ]] || { echo "Invalid choice. Please try again." >&2; continue; }
    break
done

or

read -p "Run? " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
    cowsay
fi

Print date/time string#

date -u +"%Y-%m-%dT%H:%M:%SZ"

Redirect command output
#

stderr and stdout

command > /dev/null 2>&1
# bash only
command &> /dev/null

only stderr

command 2> /dev/null

only stdout

command 1> /dev/null

Replace whitespace with underscore in all filenames in current directory
#

for f in *\ *; do mv "$f" "${f// /_}"; done

Prevent a script from exiting your shell
#

If you source a script file any exit in a function will exit the shell as it runs in the current shell instead of spawning a subshell. Prevent this behaviour by using return! When a 3rd party script you cannot modify is called which exits your shell than wrap the call with () which spawns a subshell for this call

Printf
#

More stable and powerful than echo. Comparable to C’s function.

printf "%s with %s\\n" "VAL1" "VAL2" >> test.txt

Make all files with shebang in a folder executable
#

grep -rl '^#!' FOLDER | xargs chmod +x

Empty/clear a file
#

echo > FILE
cat /dev/null > FILE

String manipulation
#

Substrings
#

echo "id=12" | cut -d'=' -f 2 # 12

Omit first line of stdout
#

awk '{if(NR>1)print}'

Find
#

Find directories containing the most amount of files
#

find . -type d -exec sh -c "fc=\$(find '{}' -type f | wc -l); echo -e \"\$fc\t{}\"" \; | sort -nr

File or directory listings
#

find PATH -maxdepth 1 -mindepth 1 -type d -printf '%f\n' | exec -0 ls -l
find PATH -maxdepth 1 -mindepth 1 -type f -not -name README.md | exec -0 ls -l

Convert all files in the directory to unix line endings
#

find . -type f -print0 | xargs -0 dos2unix

Do not list the root directory
#

find deployments ! -path deployments -type d -printf "%f\n"

basename functionality
#

find . -printf "%f\n"

Working with geopts
#

#!/bin/bash

OPTIND=1
while getopts "ewa:" opt
do
  case "$opt" in
    e) echo "ethernet" ;;
    w) echo "wifi" ;;
    a) echo $OPTARG ;;
  esac
done
shift $(expr $OPTIND - 1)

Create a simple menu with zenity
#

#!/bin/bash

choice="AA BB CC DD EE"
response=$(zenity --height=300 --width=200 --list --title='Snippet' --column=choice $choice)

case $response in
  AA)
    echo Your choice is $response
    ;;
  BB)
    echo Your choice is $response
    ;;
  CC)
    echo Your choice is $response
    ;;
  DD)
    echo Your choice is $response
    ;;
esac

Notify function for scripting

function notify {
	if [[ -z "${DISPLAY// }" ]]
	then
		# no x server detected
		echo "$1"
	elif ! [[ -z "${DISPLAY// }" ]]
	then
		# x server detected
		notify-send "$1"
	else
		# error
		echo "Cannot check for running x server"
		exit 1
	fi
}