Shell Scripts
Table of Contents
Naming conventions
-
ALL_CAPSfor variable names -
CamelCasefor function names -
thisVARIABLEormyVARIABLEfor 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
PATHthat 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
-xflag 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
.loginfile
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
/procdirectory, 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.logand thenCtrl-dto 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
Check Gentoo Development Guide or UNIX Shell Programming out.