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
================================================================