Shell Scripts

Naming conventions

  • ALL_CAPS for variable names
  • CamelCase for function names
  • thisVARIABLE or myVARIABLE for looped variables

Best practices

Modularize all repeated code statements.

Tests

if [ -z "$(commands)" ];

if the result is blank

if [ ! -z "$(commands)" ];

if the result is not blank

if [ -d "$directory" ];

if the directory exists

if [ -w "$directory" ];
if [ -f "$file" ];

if the file exists

if [ -r "$file" ];

if the file is readable (else "Cannot read file")

if [ -x "$file" ];

if the file is executable

if [ -s "$file" ];

if the file is non empty

Installing a wrapper program

There are 3 ways to invoke the new script:

  • ensure that users have a PATH that looks in local directories before it looks in the standard Unix binary distro directories
  • use a systemwide alias to have this script wrap a standard call
  • rename the underlying program and then install the wrapper using the underlying program's old name

Locking files

Any script that reads or appends to a shared data file needs a reliable way to lock the file. Many Unix distributions have lockfile installed (with the procmail package).

Shell script library

Using the . source notation to run the script, it is handled as if each command in the script was typed directly in the current shell.

If you have an exit 0 command within a script that's sourced, it will exit the shell and log out of that window.

Code

The skeleton of the scripts should look like:

#!/bin/bash
# script -- Purpose: what it does
# Exits with zero if no error.

#* Independent variables
BASE_DIR=/usr/scripts

#* Nothing to change below this line

#* Dependent variables
LOG_DIR=${BASE_DIR}/logs

# identifiable (and unpredictable) temporary file name
TEMP_FILE="/tmp/$(basename $0).$$.$RANDOM.tmp"

#* Functions

function CleanUp()
{
    # blow away TEMP_FILE
    /bin/rm -f $TEMP_FILE
}

# perform housekeeping on program exit or a variety of signals
# (EXIT, HUP, INT, QUIT, TERM)
trap CleanUp 0 1 2 3 15

function Usage()
{
    cat << EOF 1>&2
Usage: $(basename $0) flags...
Where...
EOF
    exit 1
}

#* Runtime

if [[ $# -eq 0 ]]; then
    Usage
fi

while getopts "a:b:" opt; do
    case $opt in
        a ) var1="$OPTARG" ;;
        b ) var2="$OPTARG" ;;
    esac
done
shift $(($OPTIND - 1))

while [[ $# -gt 0 ]]; do
    case $1 in
        a ) var1=val1 ;;
        b ) var2=val2 ;;
    esac
    shift  # processed flag, let's see if there's another
done

command_1
command_2
...
command_N

exit 0

# script ends here

Some tips & tricks

using the -x flag to the shell

display debugging output

>2 /dev/null

discard any error resulting from the command (rather than shown to the user)

cat /dev/null > $file

clear out the contents of the file

cut -d. -f1

left of the decimal separator

???
$(commands) can be used in "here documents"
???
add a call to some scripts in your .login file

Sed and tr

[:digit:]

digits

[:upper:]

upper characters

[:lower:]

lower characters

[:alnum:]

upper + lower + digits

=sed -e 's/[[:upper:]]//g'=

remove the upper characters and see what's left

Interesting constructions

while

Redirect the input of the while loop.

while read line
    do
        commands
    done < inputfile

for

for line in ...
do
    commands
done | awk "condition { print \$0 }"

here documents

cat << EOF | fmt | mail -s "Subject" $account
Text
EOF

Spaces in names

sed 's/ ^^^/g' and sed 's^^^/ /g'

pre- and post-processing to replace spaces in names

Use the eval directive to ensure that filenames with spaces are treated properly. When the variables are instantiated, each argument is surrounded by quotes.

eval is used to force the shell to act as if the command were typed at a command line (where the quotes are stripped once they have been utilized for arg parsing).

Links

Symbolic links are special files that store the names of the destinations. Hard links are actually assigned the same inode as the linked file.

find

xdev

don't go through all file systems, avoiding system areas, read-only source directories, removable devices, the /proc directory, and similar areas

Helpful commands to know

chattr

set a specific append-only file permission (on log files, for example)

col -b

strip embedded display formatting of man pages

exec

replace the current running shell with the application specified (when there is nothing left to do after calling the last command, this method of ending a script is more efficient than having the shell hanging around waiting for the last command to end)

fmt

reformat text (filling in paragraphs) to improve its appearance

logger

write the log messages to syslog

man -k

list the man pages whose one-line summary include the specified word

script

great way of logging everything that happens in your session. You can run script console.log and then Ctrl-d to stop recording.

strace

see what accesses the process does

sudo

execute one command as root, after which you are back to being a regular user

trap

ensure that any temp files are removed

wait

stop the script until all background commands (using the trailing & to drop each of them into its own subshell) are completed

watch

repeatedly rerun a command (every 2 seconds by default). If you use watch free, you will see the output of the free command updated every 2 seconds.

xargs

merge lines so that they become one line

More information