Bash Programming

May 20, 2019 Programming 5 minutes, 45 seconds

#!/bin/bash

set -euo pipefail

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

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

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

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

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

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

[ 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 ]

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

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

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

arr=( $string )

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

command -v PROGRAMM >/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

[ -d $(ps -A | grep 'PATTERN') ] && echo "exists" || echo "not exists"

getent passwd USER
id -u name

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; }

apt-get install postgresql-client

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

    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

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 '{gsub(/search_pattern/,x); }'

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

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

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

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

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

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

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

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

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

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

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

echo > FILE
cat /dev/null > FILE

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

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

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

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