SHELLdorado Newsletter 1/2003 - July 6th, 2003
================================================================
The "SHELLdorado Newsletter" covers UNIX shell script related
topics. To subscribe to this newsletter, leave your e-mail
address at the SHELLdorado home page:
http://www.shelldorado.com/
View previous issues at the following location:
http://www.shelldorado.com/newsletter/
"Heiner's SHELLdorado" is a place for UNIX shell script
programmers providing
Many shell script examples,
shell scripting tips & tricks + more...
================================================================
Contents
o Shell Tip: Returning more than one value from a function (or AWK)
o Shell Script: "tarmail" - send "tar" archive by e-mail
o Shell Tip: Print lines of a file in reverse (or random) order
o Shell Tip: advisory locking for shell scripts
-----------------------------------------------------------------
>> Shell Tip: Returning more than one value from a function (or AWK)
-----------------------------------------------------------------
A shell function function can return error codes, strings,
and even multiple values. How? Read on...
Returning success or failure values from a shell function is
straightforward, e.g.
# validinteger - return success if string consists only of
# numbers
validinteger () {
case "$1" in
*[!0-9]*) return 1;; # string contains invalid chars.
*) return 0;; # only digits from 0-9
esac
}
This function can be used as follows:
if validinteger "$1"
then echo "valid number: $1"
else echo "no valid integer number: $1"
fi
Command substitution can be used for a string return value, e.g. the
function "abspath":
# abspath - return absolute path name (starting with /)
abspath () {
D=`dirname "$1"`
N=`basename "$1"`
(cd "$D"; echo "`pwd`/$N")
}
The following command will resolve the directory "../bin" to an
absolute path name (e.g. /home/john/bin), and assign this value to
the variable "path":
path=`abspath "../bin"`
But can a function set more than one variable? It can, using
a little trick: the function does not directly print the
results to standard output, but emits variable assignments
instead. The calling shell has to evaluate them using, no
surprise, "eval":
# Read /etc/password, and get home directory and shell name
set_home_and_shell () {
awk -F: '
$1 == "john" {
print "home=" $6
print "shell=" $7
}
' /etc/passwd
}
Calling this function could result in an output line like the
following:
home=/home/john
shell=/bin/ksh
A caller then has to evaluate these assignments, making the
shell interpret the lines as commands:
eval "`set_home_and_shell`"
# $home and $shell are set here
-----------------------------------------------------------------
>> Shell Script: "tarmail" - send "tar" archive by e-mail
-----------------------------------------------------------------
When sending all the contents of a directory by e-mail,
nobody has to create "tar" archives manually, tediously
encode them e.g. using "uuencode", and invoke "mail"
manually. The following script does all that, resulting in
commands like
tarmail john@home.com /home/john
to send the whole directory hierarchy /home/john to the
e-mail address john@home.com.
:
############################################################
# tarmail - create "tar" archive, send it by e-mail
if [ $# -lt 2 ]
then
echo >&2 "usage: tarmail recipient {file|dir} [...]"
exit 1
fi
recipient=$1; shift
case "$recipient" in
*@*) ;; # This looks like an e-mail address
*) echo >&2 "tarmail: probably no valid e-mail address: $recipient"
exit 1;;
esac
for path
do
[ -r "$path" ] || continue
tarfile=$path.tar
{
echo "
This is an uuencoded 'tar' archive. Save it to a file
(e.g. "mail.uue"), and unpack it using
uudecode mail.uue
tar xvf $tarfile"
tar cf - "$path" |
uuencode "$tarfile"
} | mailx -s "$tarfile" "$recipient" || exit 1
done
############################################################
[ Extended version of this script:
http://www.shelldorado.com/scripts/cmds/tarmail
]
If your system does not have "mailx", you may need to use
another e-mail client that's able to set subject lines, e.g.
"Mail" (with a capital 'M') or just "mail".
The resulting mails can be read e.g. by MS Outlook, Mozilla,
and Netscape e-mail clients.
Well, using uuencode is a spartan way to send mail
attachments. A more general way is to create MIME file
attachments, which contain the type of the data that is
attached (e.g. "image/gif". It can be used by the receiving
e-mail client to automatically start the right program to
display it (whatever this may be, for "application/tar"). The
following article goes into the details, and lists some
helper programs and scripts that are useful to have:
http://www.shelldorado.com/articles/mailattachments.html
-----------------------------------------------------------------
>> Shell Tip: Print lines of a file in reverse (or random) order
-----------------------------------------------------------------
For printing lines in reverse order, i.e. the last line of a
file first, we use a simple trick: each line gets a line
number on which we sort, and then remove later on:
nl -ba < "input" | sort -nr | cut -f2-
"nl" numbers lines, "-ba" even empty ones. "sort -nr" sorts
all lines numerically on the first column, descending order.
"cut" finally removes the first sorting column.
Now the idea of printing lines in a random order is not too
far away. It's e.g. useful for processing MP3 play lists, or
creating random signature files. Instead of printing a
sequential number, we'll print a random number at the start
of each line, and sort the lines based on that number:
awk '{ print rand() " " $0 }' "input" |
sort -n | cut -f2-
Note that this requires a recent version of AWK. Solaris
users should use "nawk" ("new AWK"), GNU awk (gawk) works
fine, too.
A slightly more general version for printing lines in a
random order is available at the SHELLdorado:
http://www.shelldorado.com/scripts/cmds/shuffle
-----------------------------------------------------------------
>> Shell Tip: advisory locking for shell scripts
-----------------------------------------------------------------
Sometimes a script may want to run exclusively. A script
starting a database server may want to ensure that no other
instance of the same script is starting the server at the
same time, or a script creating backups on a tape device
could ensure that it is only run once at a time.
This kind of advisory locking for shell scripts can be
implemented by creating (temporary) directories. Directory
creation is guaranteed to be atomic, for each process it
either succeeds or it fails, but it cannot succeed for more
than one process at the same time. We take advantage of this
in the following script for advisory locking:
:
# makelock - advisory locking for shell scripts
if [ $# -ne 1 ]
then
echo >&2 "usage: makelock lockname"
exit 1
fi
lockname=$1; shift
lockpath=/tmp/$lockname
try=1 # first of 5 tries
until mkdir "$lockpath" >/dev/null 2>&1
do
try=`expr $try + 1`
if [ $try -gt 5 ]
then
echo >&2 "giving up on $lockname"
exit 1
fi
sleep 5 # give process time to release lock
done
# We now have the lock!
When the program no longer needs the lock, it can use
"rmdir /tmp/$lockname" to free it. A usage example:
if makelock Restore
then
# Free lock when program terminates (exit or signal)
trap 'rmdir /tmp/Restore' 0
trap "exit 2" 1 2 3 15
echo >&2 "restore: starting to restore data..."
# do some work...
else
echo >&2 "restore: cannot acquire lock: Restore"
exit 1
fi
Note that this kind of locking does not handle "stale" locks,
i.e. locks that have been acquired, but have not been released
by a program (e.g. because of a crash, or "kill -9").
The program "mklock" is an extended version of "makelock",
which also implements "unlocking":
http://www.shelldorado.com/scripts/quickies/mklock
----------------------------------------------------------------
If you want to comment on this newsletter, have suggestions for
new topics to be covered in one of the next issues, or even want
to submit an article of your own, send an e-mail to
mailto:heiner.steven@shelldorado.com
================================================================
To unsubscribe, send a mail with the body "unsubscribe" to
newsletter@shelldorado.com
================================================================