Shebang

#!/bin/bash

set

set -o noclobber  to enable noclobber
set +o to disable noclobber
set -f enable disble file globbing
set +f disable disble file globbing
set -x : show the command
$ pwd
+ pwd
/home/chang

Declare

  -p    display the attributes and value of each NAME   (print out)
  -a    to make NAMEs indexed arrays (if supported)
  -A    to make NAMEs associative arrays (if supported)
  -i    to make NAMEs have the `integer' attribute
  -l    to convert the value of each NAME to lower case on assignment
  -n    make NAME a reference to the variable named by its value
  -r    to make NAMEs readonly
  -t    to make NAMEs have the `trace' attribute
  -u    to convert the value of each NAME to upper case on assignment
  -x    to make NAMEs export
declare -r var1=1     # Read only
echo var1 = $var1   # var1 = 1
(( var1++ ))          # x.sh: line 4: var1: readonly variable
declare -i number   # integer 
# The script will treat subsequent occurrences of number as an integer.     
number=3
echo Number = $number     # Number = 3
number=three
echo Number = $number     # Number = 0
# Tries to evaluate the string three as an integer.
n=6/3
echo n = $n       # n = 6/3
declare -i n
n=6/3
echo n = $n       # n = 2
declare s=string  # default string
declare -i  int # integer
declare -a nums   # Array
declare -f function_name   # Function
declare -x var3=373      # Export to Env variable

Variable Default

(:-) : default value is returned when the vaiable is null or the variable is unset
(-): default value is returned when the variable is unset

set username
echo ${#username}   # 0
echo ${username:-bob}   # bob 
unset username
echo $(username-bob} #bob

Variables

HOST=$(hostname)
# User executing the script:
CURRENTUSER=$(whoami)
# Current date:
CURRENTDATE=$(date +%F)
# Host IP address:
IPADDRESS=$(hostname -I | cut -d ' ' -f1)
# SHOW MESSAGES
echo Today is $CURRENTDATE
echo Hostname: $HOST ($IPADDRESS)
echo User info for $CURRENTUSER:
grep $CURRENTUSER /etc/passwd
v=9
echo $((v + 1))

Array and Dictionary

declare -a name
name[0]=bob
name[1]=smaith
echo ${name[0]}
unset name
declare -A name  # dictionary
name=([first]=bob [last]=smith)
echo ${name[first]}

Arithmetic Evaluation

echo $((3*5))
a=$((3+5))
echo $a
r1=3; r2=2
((r1 > r2)) && echo OK

Conditional Statements for Shell Scripts

#!/bin/bash
CURRENTDAYOFTHEMONTH=$(date +%d)
if [ $CURRENTDAYOFTHEMONTH -le 10 ]; then
    echo We are within the first 10 days of the month;
elif [ $CURRENTDAYOFTHEMONTH -le 20 ]; then
    echo We are within the first 20 days of the month;
else
    echo We are within the last 10 days of the month;
fi

The For Loop

for FILE in file1.txt file2.txt file3.txt; do
    chmod 640 $FILE
done
for FILE in $(ls -1 | grep file)
do
    chmod 640 $FILE
done

The While Loop

while read LINE; do
    USERNAME=$(echo $LINE | cut -d':' -f 1)
    USERID=$(echo $LINE | cut -d':' -f 3)
    echo The UID of $USERNAME is $USERID
done < /etc/passwd
RANDOMNUM=$(shuf -i1-10 -n1)
NUMBER=0
while [ $NUMBER != $RANDOMNUM ]
do
    read -p Enter a number between 1 and 10:  NUMBER
done
echo Congratulations! Your guess was right!

single vs double blackets

[[ … ]] works only in the Korn Bash, Zsh. extension of [].
http://mywiki.wooledge.org/BashFAQ/031
[[ a = a && b = b ]] vs [ a = a && b = b ]: syntax error ==> [ a = a ] && [ b = b ]
[[ (a = a || a = b) ]] vs [ ( a = a ) ]: syntax error, () is interpreted as a subshell
[[ a < b ]]: lexicographical comparison vs [ a < b ]: Same as above. required or else does redirection like for any other command.

As a rule of thumb, [[ is used for strings and files.
file=file name
 [[ -f $file ]] && echo $file is a regular file
 [[ -d '/tmp' ]] && echo temp is dir
 [[ -e sh.sh ]] && echo  file or dir exist
[[  -ss =~ ^- ]] && echo regular expression match

If you want to compare numbers, use an ArithmeticExpression, e.g.

declare -i i=10
declare -p i
#i=0     # i is string, not work 
#declare -p i
while ((i !=0)); do
    echo $i
    ((i--))
done

File read and write

>>read
>>my name is Chang
>>echo $REPLY
read -p Type yiour name:   username  # Prompt
read -n1 -p Continue y or n:  # Accepting only one character
read -n1 -p Continue y or n:  -s  # Silent mode no type shows in screen
read -p Enter username ( more than 8 chars):  username
echo $(#username)
if [[ ! -f ./$filename ]]; then
    echo Source file not present!
    exit 1
fi
while IFS= read -r line ; do
    echo $line 
  fi
done < $filename
cat << EOF > file.txt
The current working directory is: $PWD
You are logged in as $(whoami)
EOF
echo this is a line | tee file.txt

Command line Arguments

./myscript.sh fred jones staff
$0                  $1    $2     $3
$#  # number of argument
$*  # Complete list of arguments as a single string
$@ # Complete list of arguments as an array
while (($#)); do
    echo $1
    shift
done

Getopts

getopts cd -cd #No argument needed
getopts c:d -c fred -d # C requires argument
getopts c:d: -c fred -d joe # C and D require arguments
getopts :cd -h # Begins with colon for custom help

getopts.sh -c -- fred # -- end of options, fred is an argument
while getopts ':c:d:' opt; do
    case $opt in
        c) sudo useradd -m $OPTARG
            break;;
        d) sudo userdel -r OPTARG
            break;;
        *) echo Usage: $0 [-c|-d] <user;;
    esac
done

Scheduling script

crontab -e  # edit
crontab -l # showed all the comments
30 11 * *  3 echo hello > /tmp/hello # Once a week wed at 11:30 (min) (hour) (day) (Mon) (week))
sudo sed -Ei '/^($|#)/d' /avr/spool/cron/crontabs/pi
crontab -1

cat /etc/anacrontab
Configuration file: events happen after the system boot either daily, weekly, or monthly.
1, 10, 15 minutes after boot time daily, weekly, and monthly
Dut to the shutdown, if cronjobs did not run, the jobs will run by schedulling by anacron after system boot.

# /etc/anacrontab: configuration file for anacron
# See anacron(8) and anacrontab(5) for details.
SHELL=/bin/sh
HOME=/root
LOGNAME=root
# These replace cron's entries
1   5   cron.daily  run-parts --report /etc/cron.daily
7   10  cron.weekly run-parts --report /etc/cron.weekly
@monthly    15  cron.monthly    run-parts --report /etc/cron.monthly

Service Units

Systemd service units describe the excution of services. /etc/stsemd/system
Systemd is PID1

Service As an Example

Creating a pipe file we can communicate with client. a client send data to the pipe, TERM1. Server, TERM2, process the data. Unlike a standard pipe this allows for IPC or inter-process communication. The second command writes to the pipe (blocking). The & puts this into the background so you can continue to type commands in the same shell. It will exit when the FIFO is emptied by the next command.

sudo mkfifo /var/log/pipe
sudo cmod 666 /var/log/pipe
echo hello > var/log/pipe &
script.sh

Any data sent to the pipe will be echoed back in lowercase to pipe.out

#!/bin/bash
declare -l line
until [[ $line == stop ]]; do
    line=$(cat /var/log/pipe)
    echo $line >> /var/log/pip.out
done

sudo mv script.sh /root/bin/
sudo chmod 755 /root/bin/script.sh

Service Unit

vi script.service

[Unit]
Description=Demo pipe processing service
After=sshd.service
[Service]
Type=simple
ExecStart=/root/bin/script.sh
ExecStop=/bin/kill $MAINPID
killMode=process
[Install]
WantedBy=multi-user.target

Running script service

sudo mv script.service /etc/systemd/system
sudo systemctl daemon-reload
sudo systemctl enable --now script.service
sudo systemctl status script.service
echo HelloWord > /var/log/pipe
cat /var/log/pipe.out