Bash Scripting tips and snippets

1. Tip to get the absolute directory of the shell script

# Add the following three lines to get the name
# and absolute directory of your script.
# The redirection to /dev/null is needed as sometimes cd returns an output
scriptName=$(basename $0)
scriptDir=$(dirname $0)
scriptDir=$(cd $scriptDir > /dev/null 2>&1; pwd)

echo $scriptName
echo $scriptDir

2. Using getopt to parse a script command line with options

#!/bin/bash

# Options:      getOpt_example [-a | -b ] [-h] [args]
# Examples
#       Valid
#               ./getOpt_example.sh
#               ./getOpt_example.sh -a
#               ./getOpt_example.sh -ab abcd
#               ./getOpt_example.sh -ab abcd xyz
#               ./getOpt_example.sh -ab abcd xyz dddd
#               ./getOpt_example.sh xyz dddd -a
#               ./getOpt_example.sh xyz dddd -a xyz
#       Invalid
#               ./getOpt_example.sh -ab
#       This is valid, but will yield a wrong result
#        (bArg will be "a" and there will be no "-a" option)
#               ./getOpt_example.sh -ba xyz

scriptName=$(basename $0)
scriptDir=$(dirname $0)
scriptDir=$(cd $scriptDir > /dev/null 2>&1; pwd)

aOpt=0
bOpt=0
bArg=""
otherArgs=""

parseCmdline()
{
        USEROPTS=`getopt -o ab:h -n "$scriptName" -- "$@"`
        eval set -- "$USEROPTS"

        while [ 1 ]; do
                case "$1" in
                        -a)     aOpt=1; shift ;;
                        -b) bOpt=1; bArg=$2; shift 2 ;;
                        -h) echo "Display help and exit"; exit ;;
                        --) shift ; break ;;
                        *)  echo "Display invalid opt error, help and exit"; exit ;;
                esac
        done

        otherArgs=$*
}

parseCmdline $*

echo "aOpt=$aOpt, bOpt=$bOpt bArg=$bArg, otherArgs=$otherArgs"

3. Various ways of getting “dates”

date_today=`date +"%m-%d-%Y"`
printf "\t\tdate_today =\t%s\n" $date_today

date_yesterday=`date -d "1 day ago" +"%m-%d-%Y"`
printf "\tdate_yesterday =\t%s\n" $date_yesterday

date_tomorrow=`date -d "1 day" +"%m-%d-%Y"`
printf "\t\tdate_tomorrow =\t%s\n" $date_tomorrow

date_10_days_from_May1st=`date -d "05/01/2015 + 10 days" +"%m-%d-%Y"`
printf "date_10_days_from_May1st =\t%s\n" $date_10_days_from_May1st

date_1_year_from_today=`date -d "1 year" +"%m-%d-%Y"`
printf "date_1_year_from_today =\t%s\n" $date_1_year_from_today

thismonth=`date +%m`
nextmonth=`expr $thismonth + 1`
useyear=`date +%Y`
if [ $nextmonth -eq 13 ]; then
        nextmonth=`expr $nextmonth - 12`
        useyear=`$expr $useyear + 1`
fi
date_last_day_of_this_month=`date -d "$nextmonth/1/$useyear 1 day ago" +"%m-%d-%Y"`
printf "date_last_day_of_this_month =\t%s\n" $date_last_day_of_this_month

thismonth=`date +%m`
twomonths=`expr $thismonth + 2`
useyear=`date +%Y`
if [ $twomonths -ge 13 ]; then
        twomonths=`expr $twomonths - 12`
        useyear=`$expr $useyear + 1`
fi
date_last_day_of_next_month=`date -d "$twomonths/1/$useyear 1 day ago" +"%m-%d-%Y"`
printf "date_last_day_of_next_month =\t%s\n" $date_last_day_of_next_month

thismonth=`date +%m`
useyear=`date +%Y`
date_last_day_of_prev_month=`date -d "$thismonth/1/$useyear 1 day ago" +"%m-%d-%Y"`
printf "date_last_day_of_prev_month =\t%s\n" $date_last_day_of_prev_month

4a. Read one line from a file (option 1)

# Read this script itself
# Any "leading" or "trailing" spaces from every line will be removed
cat $0 | \
while read oneLine; do
        echo "|$oneLine|"
done

4b. Read one line from a file (option 2) – a much longer way of doing it, but one I have used a lot, hence adding it here

this_script="$0"

file_len=$(cat $this_script | wc -l)
file_len=$(expr $file_len + 0)  # convert to integer

i=1
while [ $i -le $file_len ]; do
    one_line=$(head -$i $this_script | tail -1)
    echo "|$one_line}"

    i=$(expr $i + 1)
done

4c. Read one line from a file (option 3). Using a “bash” specific trick.

this_script="$0"

save_IFS="${IFS}"
IFS=$'\n'
# This for loop will print one line at a time
for one_line in $(cat $this_script)
do
        echo "|$one_line|"
done
IFS="${save_IFS}"
# This for loop will revert to the default and print one word at a time
for one_line in $(cat $this_script)
do
        echo "|$one_line|"
done

5. Access arrays in scripts

arr[1]="arr1"
arr[2]="arr2"
arr[3]="arr3"
arr[4]="arr4"
arr[5]="arr5"

i=1
while [ $i -le 5 ]; do
        echo "${arr[$i]}"  # Display an array element
        garr[$i]="garr${i}"  # Create an array element dynamically
        i=`expr $i + 1`
done

i=1
while [ $i -le 5 ]; do
        echo "${garr[$i]}"   # Display the dynamic array element
        i=`expr $i + 1`
done

6a. Age files by deleting files older than “n” days. Option 1 – assuming you have the program “tmpwatch” installed on your machine.

#!/bin/bash
scriptname=`basename $0`
scriptdir=`dirname $0`
scriptdir=`(cd $scriptdir > /dev/null 2>&1; pwd)`

usage()
{
  echo "Usage: $scriptname   []"
  echo ""
  echo "  BaseDir: All files under this dir will be deleted"
  echo "  Num Days: Delete files not accessed in Num Days (not created or modified, but accessed)"
  echo "  Also Delete Dirs and Symlinks: Specify anything as 3rd arg to cause this"
  echo ""
  echo "Eg.: $scriptname /tmp 10 --- Delete all files under /tmp not accessed in the last 10 days"
  echo "     $scriptname /tmp 10 dummy --- same as above, but also clean old dirs and symlinks"
}

BaseDir=$1
OlderThanDays=$2
AlsoDeleteDirectories=$3

if [ "x$BaseDir" = "x" ]; then
  echo "No base directory specified"
  usage
  exit 1
fi

if [ "x$OlderThanDays" = "x" ]; then
  echo "Specify how many days you want to keep files around"
  usage
  exit 1
fi

tmpwatch_exe=`which tmpwatch 2> /dev/null`
if [ "x$tmpwatch_exe" = "x" ]; then
  tmpwatch_exe=/usr/sbin/tmpwatch
fi

if [ ! -x $tmpwatch_exe ]; then
  echo "Unable to find the executable tmpwatch in path or in /usr/sbin"
  exit 1
fi

monthDay=`date +"0%0d%^b"`
logfile=/tmp/age_files.log
errfile=/tmp/age_files.err
tmpwatch_options=""
tmpwatch_options="$tmpwatch_options --verbose"
if [ "x$AlsoDeleteDirectories" = "x" ]; then
  tmpwatch_options="$tmpwatch_options --nodirs --nosymlinks"
fi
tmpwatch_options="$tmpwatch_options --fuser"    # Attempt  to  use the /sbin/fuser
                                                # to see if a file is already open before
                                                # removing it.
# tmpwatch_options="$tmpwatch_options --test"   # Uncomment to just test what will happen

hours=`expr $OlderThanDays \* 24`
echo "$tmpwatch_exe $tmpwatch_options $hours $BaseDir" > $logfile
$tmpwatch_exe $tmpwatch_options $hours $BaseDir 2> $errfile >> $logfile
cat $errfile >> $logfile

6b. Age files by deleting files older than “n” days. Option 2 – Using the traditional “find” method

TBD

7. There have been multiple times when I have had the need to create a “scheduled backup” script on my many UNIX servers. Of course, the goto program to use for the same is rsync, but I have found myself always creating scripts with multiple rsync entries and other variations to skip folders etc. So, I ended creating a “shell include” script which expects certain variables to be defined, and then it would perform the rsync to either a remote server or local server. I have attached a zip file with the .shinc (shell include) and a sample test script.

doRsync.shinc zip file

8.

Leave a Reply