#!/bin/sh
# see also `man sh` and `man bash`
# see https://wiki.ubuntu.com/DashAsBinSh for bash specific (non-POSIX) features

## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## EXPANSIONS
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# bc (1) - An arbitrary precision calculator language

## tilde expansion

echo ~      # /home/gokcehan
echo ~foo   # ~foo
echo ~/foo  # /home/gokcehan/foo
echo foo~   # foo~
echo foo/~  # foo/~
echo '~'    # ~
echo "~"    # ~

## parameter expansion

foo="hello"

echo $foo       # hello
echo $foobar    # (prints nothing since there is no $foobar)
echo ${foo}bar  # hellobar
echo '$foo'     # $foo
echo "$foo"     # hello

## command substitution

which ls          # /bin/ls
ls -l /bin/ls     # -rwxr-xr-x 1 root root 133792 Oca 18  2018 /bin/ls
ls -l `which ls`  # -rwxr-xr-x 1 root root 133792 Oca 18  2018 /bin/ls

echo `which ls`            # /bin/ls
echo `ls -l \`which ls\``  # -rwxr-xr-x 1 root root 133792 Oca 18  2018 /bin/ls
echo $(which ls)           # /bin/ls
echo $(ls -l $(which ls))  # -rwxr-xr-x 1 root root 133792 Oca 18  2018 /bin/ls
echo '`which ls`'          # `which ls`
echo "`which ls`"          # /bin/ls

## arithmetic expansion

echo 2+2                   # 2+2
echo $((2+2))              # 4
echo $((4/2))              # 2
echo $((5/2))              # 2
echo '5/2' | bc            # 2
echo 'scale=10; 5/2' | bc  # 2.5000000000
echo '5/2' | bc -l         # 2.50000000000000000000

## glob expansion

echo *       # foo foo.c bar bar.c ...
echo *.c     # foo.c bar.c ...
echo *.[ch]  # foo.c bar.h ...
echo *.?     # foo.c bar.h baz.a ...
echo '*'     # *
echo "*"     # *

## brace expansion (bash only -- not POSIX standard)

echo foo.{c,h}    # foo.c foo.h
echo foo{.c,}     # foo.c foo
echo 'foo.{c,h}'  # foo.{c,h}
echo "foo.{c,h}"  # foo.{c,h}

## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## CONDITIONALS
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# true (1) - do nothing, successfully
# false (1) - do nothing, unsuccessfully
# test (1) - check file types and compare values
# [ (1) - check file types and compare values
# uname (1) - print system information

# run an example command
echo hello world
# hello world

# show the return value of last command (0 is true/success, non-0 is false/fail)
echo $?
# 0

# TIP: you may put the following in your '~/.bashrc' to display error codes
EC() { echo -e '\e[1;33m'code $?'\e[m\n'; }
trap EC ERR

# statement separator ';' is optional at the end of line
echo hello;
# hello

# use ';' to separate multiple statements in a single line
echo hello; echo world
# hello
# world

# run 'true' and show its return value
true; echo $?
# 0

# run 'false' and show its return value
false; echo $?
# 1

# run 'echo' command since first condition is true (short-circuit evaluation)
true && echo 'is true'
# is true

# skip 'echo' command since first condition is false (short-circuit evaluation)
false && echo 'is false'
# (no output)

# run first 'echo' command since first condition is true (short-circuit evaluation)
true && echo 'is true' || echo 'is false'
# is true

# run second 'echo' command since first condition is false (short-circuit evaluation)
false && echo 'is true' || echo 'is false'
# is false

# check whether '/bin' exists
test -e /bin; echo $?
# 0

# check whether '/bin/ls' exists
test -e /bin/ls; echo $?
# 0

# check whether '/bin' exists as a file
test -f /bin; echo $?
# 1

# check whether '/bin/ls' exists as a file
test -f /bin/ls; echo $?
# 0

# check whether '/path/to/non/existent/file' exists as a file
test -f /path/to/non/existent/file; echo $?
# 1

# check whether '/bin' exists as a directory
test -d /bin; echo $?
# 0

# check whether '/bin/ls' exists as a directory
test -d /bin/ls; echo $?
# 1

# check whether '/path/to/non/existent/dir' exists as a directory
test -d /path/to/non/existent/dir; echo $?
# 1

# check whether '/bin/ls' is readable
test -r /bin/ls; echo $?
# 0

# check whether '/bin/ls' is writable
test -w /bin/ls; echo $?
# 1

# check whether '/bin/ls' is readable AND writable
test -r /bin/ls -a -w /bin/ls; echo $?
# 1

# check whether '/bin/ls' is readable OR writable
test -r /bin/ls -o -w /bin/ls; echo $?
# 0

# check whether '/bin/ls' is NOT readable
test ! -r /bin/ls; echo $?
# 1

# check whether 'HOSTNAME' variable is of non-zero length
test -n $HOSTNAME; echo $?
# 0

# check whether 'HOSTNAME' variable is of zero length
test -z $HOSTNAME; echo $?
# 1

# check whether the kernel is 'Linux'
test $(uname -s) = "Linux"; echo $?
# 0

# check whether the machine is 'x86_64'
test $(uname -m) != "x86_64"; echo $?
# 1

# check whether '/bin' exists (alternative syntax)
[ -e /bin ]; echo $?
# 0

# you need a space after '['
[-e /bin ]; echo $?
# /bin/bash: [-e: command not found

# you need a space before ']'
[ -e /bin]; echo $?
# /bin/bash: line 0: [: missing `]'

# you need a space before '='
[ $(uname -m)= "x86_64" ]; echo $?
# /bin/bash: line 0: [: x86_64=: unary operator expected

# you need a space after '='
[ $(uname -m) ="x86_64" ]; echo $?
# /bin/bash: line 0: [: x86_64: unary operator expected

## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## CONTROL FLOW
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# basename (1) - strip directory and suffix from filenames
# seq (1) - print a sequence of numbers

## if/elif/else/fi

arch=$(uname -m)

if [ $arch = "x86_64" ]
then
    echo 'system is 64 bit'
fi
# system is 64 bit

if [ $arch = "x86_64" ]; then
    echo 'system is 64 bit'
fi
# system is 64 bit

if [ $arch = "x86_64" ]; then echo 'system is 64 bit'; fi
# system is 64 bit

if [ $arch = "x86_64" ]; then
    echo 'system is 64 bit'
elif [ $arch = "i686" ]; then
    echo 'system is 32 bit'
fi
# system is 64 bit

if [ $arch = "x86_64" ]; then
    echo 'system is 64 bit'
elif [ $arch = "i686" ]; then
    echo 'system is 32 bit'
else
    echo 'system is unknown'
fi
# system is 64 bit

## while/until/done

i=0
while [ $i -lt 5 ]; do
    sleep 1
    i=$((i+1))
    echo "$i second(s) have passed"
done
# 1 second(s) have passed
# 2 second(s) have passed
# 3 second(s) have passed
# 4 second(s) have passed
# 5 second(s) have passed

i=0
until [ $i -ge 5 ]; do
    sleep 1
    i=$((i+1))
    echo "$i second(s) have passed"
done
# 1 second(s) have passed
# 2 second(s) have passed
# 3 second(s) have passed
# 4 second(s) have passed
# 5 second(s) have passed

## for/done

for fruit in apple orange banana; do
    echo $fruit
done
# apple
# orange
# banana

for fruit in 'apple orange banana'; do
    echo $fruit
done
# apple orange banana

for fruit in "apple orange banana"; do
    echo $fruit
done
# apple orange banana

for fruit in 'red apple' 'orange' 'banana'; do
    echo $fruit
done
# red apple
# orange
# banana

for f in *.c; do
    echo $f
done
# foo.c
# bar.c
# baz.c

# parsing 'ls' output can NOT handle whitespaces in filenames
for f in $(ls ~); do
    echo "$f"
done
# cmpe230
# Desktop
# Documents
# Downloads
# examples.desktop
# Music
# Pictures
# Public
# Templates
# Videos
# VirtualBox
# VMs

# use glob expansion instead of 'ls' for safety
for f in $HOME/*; do
    echo "$f"
done
# /home/gokcehan/cmpe230
# /home/gokcehan/Desktop
# /home/gokcehan/Documents
# /home/gokcehan/Downloads
# /home/gokcehan/examples.desktop
# /home/gokcehan/Music
# /home/gokcehan/Pictures
# /home/gokcehan/Public
# /home/gokcehan/Templates
# /home/gokcehan/Videos
# /home/gokcehan/VirtualBox VMs

# show the basename of '/path/to/file/name.txt'
basename /path/to/file/name.txt
# name.txt

# show the basename of '/path/to/file/name.txt' and strip '.txt' extension
basename /path/to/file/name.txt .txt
# name

# generate a sequence to 5
seq 5
# 1
# 2
# 3
# 4
# 5

# generate a sequence from 2 to 5
seq 2 5
# 2
# 3
# 4
# 5

# generate a sequence from 2 to 9 with a step of 3
seq 2 3 9
# 2
# 5
# 8

# you can use 'continue' and 'break' in loops
for i in $(seq 10); do
    if [ $i -eq 3 ]; then
        continue
    elif [ $i -eq 5 ]; then
        break
    fi
    echo $i
done
# 1
# 2
# 4

## case/esac

# Each body is finished with ';;'
# Left '(' is optional
# Multiple options can be given with '|'
# Globs can be used with patterns
# Everything else can be given with '*)'

os=$(uname -s)
case $os in
    ("Linux")
        echo "setting up for Linux"
        ;;
    "Darwin")
        echo "setting up for MacOSX"
        ;;
    "NetBSD"|"FreeBSD")
        echo "setting up for BSD"
        ;;
    *)
        echo "unsupported system"
        ;;
esac
# setting up for Linux

## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## FUNCTIONS
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# define a function named 'greet'
greet() {
    echo hello $1
}
# (function is defined)

# define a function named 'greet' (bash only -- not POSIX standard)
function greet {
    echo hello $1
}
# (function is defined)

# statement separator ';' is required for a single line definition
greet() { echo hello $1; }
# (function is defined)

# run 'greet' function with the argument 'world'
greet world
# hello world

# define a function named 'extract' to extract an archive using its extension
extract() {
    case "$1" in
        *.tar.bz|*.tar.bz2|*.tbz|*.tbz2) tar xjf "$1";;
        *.tar.gz|*.tgz) tar xzf "$1";;
        *.tar.xz|*.txz) tar xJf "$1";;
        *.zip) unzip "$1";;
        *.rar) unrar x "$1";;
        *.7z) 7z x "$1";;
    esac
}
# (function is defined)

# run 'extract' function with the argument 'foo.tar.gz'
extract foo.tar.gz
# (archive file is extracted)