SHELLdorado Newsletter 1/2001 - January 12, 2001
================================================================
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/
"Heiner's SHELLdorado" is a place for UNIX shell script
programmers providing
Many shell script examples,
shell scripting tips & tricks + more...
================================================================
Main Topic: Shell Functions
Contents
o What's new at the SHELLdorado?
o Shell Tip: Progress indicator
o Shell Tip: Use function "echon()" to "echo" without newline
o Shell Tip: Shell function "ask()" for yes/no questions
o Q&A: How can I search for text in all subdirectories?
o Q&A: How can I use arguments with KornShell aliases?
o Programmer's Playground: KornShell FTP Library
-----------------------------------------------------------------
>> What's new at the SHELLdorado?
-----------------------------------------------------------------
o The "SHELLdorado Links" section has been extended. Five
new categories with more than 40 new links to interesting
shell scripting resources have been added:
http://www.shelldorado.com/links/
Thanks to Brian Hiles <brian_hiles@rocketmail.com> for
providing his extensive collection of shell scripting links,
which turned out to contain some real gems.
o The "AWK Compatibility List" has been extended and now
compares the AWK features for more than 20 operating systems
and versions:
http://www.shelldorado.com/articles/awkcompat.html
If you run an operating system not listed there, you
may help complete the list. Just run the feature test
script from the page, and send the resulting output to
mailto:heiner.steven@odn.de .
-----------------------------------------------------------------
>> Shell Tip: Progress indicator
-----------------------------------------------------------------
Sometimes we have a script taking a long time to complete. We
would like to provide some kind of progress indicator for
the impatiently waiting user threading to terminate our
script. This is the important use of "busy"! The script
writes a character to the screen once a second, so the user
knows the script is still running:
#! /bin/ksh
# busy - print status indicator
#
# usage:
# busy& busypid=$!
# somelongrunningcommand
# kill $busypid >/dev/null 2>&1
set -A Indicators -- "|" "/" "-" "\\"
integer n=${#Indicators[@]}
integer i=0
while true
do
print -n -- "${Indicators[i]"
((i=(i+1)%n))
sleep 1
done
Note that this script uses KornShell specific features
and requires a KornShell or BASH.
-----------------------------------------------------------------
>> Shell Tip: Use function "echon()" to echo without newline
-----------------------------------------------------------------
The "echo" command prints a string to standard output, and
positions the cursor at the start of the next line. Sometimes
this is not desired. Consider the following code fragment:
echo "Are you sure?"
read answer
This would result in the following output:
Are you sure?
X
('X' is the position of the cursor).
On BSD systems, we could use "echo -n" to suppress the
newline character at the end of the string, and System V
systems use "echo ...\c" for the same purpose.
The following shell function "echon()" ("echo, no newline")
automatically detects the required "echo" arguments, hence
we can simply write:
echon "Are you sure? "
to get
Are you sure? X
The function echon() is defined as follows:
echon () {
if [ X"$ECHON" = X ]
then
# Determine how to "echo" without newline: "echo -n"
# or "echo ...\c"
if [ X`echo -n` = X-n ]
then ECHON=echo; NNL="\c"
else ECHON="echo -n"; NNL=""
fi
fi
$ECHON "$*$NNL"
}
-----------------------------------------------------------------
>> Shell Tip: Use function "ask()" for yes/no questions
-----------------------------------------------------------------
Script programmers often have to prompt the user for yes/no
answers, e.g.
echo "Do you really want to remove 1230 files (yes/no)?"
The script "ask()" allows for easy prompting, e.g.
if ask -y "really remove $n files"
then
rm -f "$allfiles"
fi
Just copy the following code into your script to use the
function:
ask() {
#
# ask a question with a yes/no answer;
# Usage: ask [-y|-n] "question"
# Set default: -y = yes; -n = no; otherwise no default;
# Returns: 0 (true) = yes; 1 (false) = no;
# Note: changing the default does not effect the return value;
#
ASK_DFLT=
# process options/args
for ASK_OPT do
case "$ASK_OPT" in
-[yY]*) ASK_DFLT='y' ;;
-[nN]*) ASK_DFLT='n' ;;
--) shift; break ;;
-*) ;;
*) break
esac
shift
done
ASK_PROMPT="$*"
# get the response
while : ; do
echon "$ASK_PROMPT (y/n)?${ASK_DFLT:+ [$ASK_DFLT]} " >&2
read ASK_ANSWER ASK_JUNK
: ${ASK_ANSWER:=$ASK_DFLT}
case "$ASK_ANSWER" in
[yY]*)
return 0
;;
[nN]*)
return 1
;;
*)
echo "" >&2
esac
done
return
}
[Thanks to Daniel E. Singer <des@cs.duke.edu> for providing
the "ask()" function]
-----------------------------------------------------------------
>> Q&A: How can I search for text in all subdirectories?
-----------------------------------------------------------------
The "grep" command searches for a pattern in all specified
files, e.g.
grep "unix" *.txt
But how can we search the string in all files of the current
directory and all subdirectories?
The "find" command can be used to recurse into all
subdirectories, and the following line shows us how to
invoke the "grep" command for each file:
find . -type f -exec grep "unix" {} \;
The curly braces {} will be replaced with the current file
name. Unfortunately this will cause one "grep" invocation
for each file, resulting in a very slow execution. Most
systems have the "xargs" command we can use to speed up
the command considerably:
find . -type f -print | xargs grep "unix"
-----------------------------------------------------------------
>> Q&A: How can I use arguments with KornShell aliases?
-----------------------------------------------------------------
CSH users often write small one-line "aliases" for frequently
entered commands, e.g. "alias ll ls -l". The KornShell knows
aliases, too, but there is no way to access the arguments
to the alias. The following alias will not work as expected:
$ alias makebackup="cp $1 $1.bak"
$ makebackup testfile
To solve this problem, shell functions should be used
instead of aliases:
makebackup () { cp "$1" "$1".bak; }
function makebackup { cp "$1" "$1".bak; }
The first version works with Bourne Shell, KornShell,
and BASH. The second syntax requires KornShell or BASH.
If you include one of these lines in the file $HOME/.profile,
the command "mkbackup" can be used like a standard command.
-----------------------------------------------------------------
>> Programmer's Playground: KornShell FTP Library
-----------------------------------------------------------------
The "Programmer's Playground" features advanced shell
scripting topics.
Scripts are frequently used to automate file transfers,
e.g. using the external "ftp" program:
ftp -i -v targethost <<EOF
bin
put file.tar /tmp
quit
EOF
(This example assumes a $HOME/.netrc file with authentication
information for the host "targethost").
One disadvantage of this solution is the error checking:
only the exit code of the "ftp" command (or the analysis
of a log file) can tell us if the transfer was successful.
The KornShell FTP library (ftplib.ksh) provides FTP functions
like ftp_login, ftp_put, ftp_get, ... and allows for a
better errors handling:
#! /usr/bin/ksh
. ftplib.ksh # include library
ftplib # initialize it
if ftp_open targethost
then
ftp_login anonymous geheim || echo "$ftp_response"
ftp_put file.tar /tmp || echo "$ftp_response"
ftp_quit
ftp_close
fi
Note that the library is only a case study and cannot be
used for serious programming (yet). Interested readers
can download it from the following location:
http://www.shelldorado.com/misc/
----------------------------------------------------------------
If you want to comment on the newsletter, or even want to submit
an article of your own, send a mail to
mailto:heiner.steven@shelldorado.com
================================================================
To unsubscribe send a mail with the body "unsubscribe" to
newsletter@shelldorado.com
================================================================