Shell 2 - Basic Shell Script and Automation Task

By Sheldon L Published at 2019-11-19 Updated at 2019-11-19


Basic Syntax of Bash

#!/bin/csh
#!/bin/ksh
#!/bin/zsh
#!/usr/bin/python

Shebang

cat << EOF > hello.sh
> #!/bin/bash
> echo "Hello, world"
> EOF
bash hello.sh

# OR
chmod +x HelloWorld.sh
./HelloWorld.sh

Variables

#!/bin/bash
MY_SHELL = "bash"
echo "I like the $MY_SHELL shell."
echo "I am ${MY_SHELL}ing on my keyboard."
#!/bin/bash
SERVER_NAME=$(hostname)
echo "Running this script on ${SERVER_NAME}."
ls /lib/modules/`uname -r`

Built-In Shell Commands

Positional Prameters

Parameter Meaning
$0 Script name
$1, $2, $3, etc. First, second, third parameter, etc.
$* All parameters
$# Number of arguments
$@ from $1 to the last one
#!/bin/bash

echo "Executing script: $0"
echo "Archiving user: $1"

# Lock the account
passwd -l $1

# Create an archive of the home directory
tar cf /archives/${1}.tart.gz/home/${1}
./archive_user.sh user_name
#!/bin/bash

echo "Exacuting script: $0"

for USER in $@
do
     echo "Executing script: $0"
     echo "Archiving user: $USER"

     # Lock the account
     passwd -l $USER

     # Create an archive of the home directory
     tar cf /archives/${USER}.tart.gz /home/${USER}
done
./archive_user.sh user_name_1 user_name_2 user_name_n

Getting User Input

#!/bin/bash

read -p "Enter a user name:" USER
echo "Archiving user: $USER"

passwd -l $USER
tar cf /achives/${USER}.tar.gz /home/${USER}

Exit Statuses and Return Codes

$?

HOST = "google.com"
ping -c 1 $HOST
RETURN_CODE = $?

if ["$RETURN_CODE" -ne "0"]
then
     echo "$HOST unreachable"
fi

The exit Command

HOST="google.com"
ping -c 1 $HOST
if [ "$?" -ne "0" ]
then
  echo "HOST unreachable."
  exit 1
fi

Functions

#!/bin/bash

function fuction_name(){
     commands...
}
# OR
fuction_name(){
     commands...
}

# call function
function_name

Arithmetic Expressions

String Manipulation

${#string}          # the length of string in the variable myLen1.
${string:0:n}       # slicing
${string#*.}        # extract all characters in a string after a dot (.)

NAME=Eddie.Haskel
first=${NAME:0:5} ; echo "first name = $first"
last=${NAME#*.} ; echo "last name = $last"

The Constructs

The test and if statement

# Files and directorys
[[ -e file ]]      # True if exists
[[ -d file ]]      # True if is a directory
[[ -f file ]]      # True if exists and is a regular file (i.e. not a symbolic link, device node, directory, etc.)
[[ -s file ]]      # True if exists and s of non-zero size.

[[ -r file ]]      # True if is readable
[[ -w file ]]      # True if is writable
[[ -x file ]]      # True if is executable
[[ -g file ]]      # True if the file has sgid set.
[[ -u file ]]      # True if the file has suid set.

# Strings Testing
[ -z string ]         # True if is empty
[ -n string ]         # True if is not empty4
# note if $str1 is empty, the test [ -z $str1 ] would fail
#                              but [[ -z $str1 ]] succeeds

[[ string1 == string2 ]]
[[ string1 != string2 ]]
[[ string1 > string2 ]] # the sorting order of string1 and string2

# Numerical Tests
[[ arg1 -eq arg2 ]]     # =
[[ arg1 -ne arg2 ]]     # !=
[[ arg1 -lt arg2 ]]     # <
[[ arg1 -le arg2 ]]     # <=
[[ arg1 -gt arg2 ]]     # >
[[ arg1 -ge arg2 ]]     # >=
if [ CONDITION ]
then
  commands...
elif [ CONDITION ]
then
  commands...
else
  commands...
fi
#!/bin/bash
MY_SHELL="bash"

if [ "$MY_SHELL"="bash" ]  # it's a best practice to enclose variables in quotes
# to prevent some unexpected side effects
# when performing conditional tests
then
  echo "You seem to like the bash shell."
elif [ "$MY_SHELL"="csh" ]
then
  echo "You seem to like the csh shell."
else
  echo "You don't seem to like the bash or csh shells."
fi

AND and OR (&& and ||)

#!/bin/bash

HOST = "google.com"
ping -c 1 $HOST && echo "$HOST reachable"
#!/bin/bash

HOST = "google.com"
ping -c 1 $HOST || echo "$HOST unreachable"

Chain Commands Together

make ; make install ; make clean

if [[ CONDITION1 ]] ; then do_commands
elif [[ CONDITION2 ]] ; then do_commands
else
  do_commands
fi
make && make install && make clean
[[ CONDITION1 ]] && [[ CONDITION2 ]] && do_commands
If the first command fails, the second one will never be executed. A final refinement is to use the   (or) operator, as in:
cat file1 || cat file2 || cat file3

The case Statement

case expression in
   pattern1) execute commands;;
   pattern2) execute commands;;
   pattern3) execute commands;;
   pattern4) execute commands;;
   * )       execute some default commands or nothing ;;
esac

#!/bin/bash
echo "Do you want to destroy your entire file system?"
read response

case "$response" in
  "yes" | "y" | "Yes" | "Y" )  echo "I hope you know what you are doing!"
                               echo "I am supposed to type: rm -rf /"
                               echo "But I refuse to let you commit suicide.";;
  "no" | "n" | "No" | "N" )    echo "You are smart! Aborting...";;
  * )                          echo "You have to say yes or no.";;
esac
exit 0

The for statements

for VARIABLE in ITEM_1 ITEM_2 ITEM_n
do
     commands...
done
#!/bin/bash
COLORS="red green blue"

for COLOR in $COLORS
do
     echo "Color: $COLOR"
done
#!/bin/bash
PICTURES=$(ls *jpg)
DATE=$(date +%F)

for PICTURE in $PICTURES
do
     echo "Renaming '${PICTURE}' to '${DATE}-${PICTURE}'"
     mv ${PICTURE} ${DATE}-${PICTURE}
done

The while statements

while [ condition is true ]
do
  commands
done

#!/bin/bash
n=$1
[ "$n" == "" ] && echo "Please give a number and try again" && exit 1

factorial=1 ; j=1

while [ $j -le $n ]
do
  factorial=$(( $factorial * $j ))
  j=$(( $j + 1 ))
done

echo "The factorial of $n, $n! = $factorial"

Thi until statements

until [ condition is false ]
do
  commands
done

#!/bin/bash
n=$1
[ "$n" == "" ] && echo "Pleas give a number and try again" && exit 1

factorial=1 ; j=1

until [ $j -gt $n ]
do
  factorial=$(( $factorial * $j ))
  j=$(( $j + 1 ))
done

echo "The factorial of $n, $n! = $factorial"

Debugging bash Scripts

Redirecting Errors to File and Screen

bash [script].sh 2> error.log  # because the File Descriptor of `stderr` is 2, see "file stream"
# default redirect File Descriptor is 1, so `>` will redirect to `stdout`, whose descriptor is 1.

Scheduling Future Processes

$ at now + 2 days
at> cat file1.txt
at> <EOT>          # press Ctrl+D here


# CROND     #  driven by /etc/crontab (cron table), run every day/week/month...

crontab -e   # 0 10 * * * /tmp/myjob.sh
# OR
touch mycrontab
echo `0 10 * * * /tmp/myjob.sh` > mycrontab
crontab mycrontab

crontab -l  # list
crontab -r  # remove

touch /tmp/myjob.sh
echo '#!/bin/bash' > /tmp/myjob.sh
echo 'echo Hello I am running $0 at $(date)' >> /tmp/myjob.sh
chmod +x /tmp/myjob.sh


sleep [number]<s|m|h|d>      # suspends execution for at least the specified period of time

Additional Useful Techniques

Creating Temporary Files and Directories

# file
TEMP=$(mktemp /tmp/tempfile.XXXXXXXX)  # XXXXXXXX will be replaced by mktemp

# dir
TEMPDIR=$(mktemp -d /tmp/tempdir.XXXXXXXX)
# For example, if someone were to create a symbolic link from a known temporary file
# used by root to the `/etc/passwd` file, like this:
ln -s /etc/passwd /tmp/tempfile

# There could be a big problem if a script run by root has a line in like this:
echo $VAR > /tmp/tempfile
# The password file will be overwritten by the temporary file contents.

# To prevent such a situation, make sure you randomize your temporary file names
# by replacing the above line with the following lines:
TEMP=$(mktemp /tmp/tempfile.XXXXXXXX)
echo $VAR > $TEMP

Discarding Output with /dev/null

ls -lR /tmp > /dev/null  # only stdout will be dumped into /dev/null.

ls -lR /tmp >& /dev/null # both stdout and stderr will be dumped into /dev/null.

Random Numbers and Data