1673 lines
26 KiB
HTML
1673 lines
26 KiB
HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
|
<HTML
|
|
><HEAD
|
|
><TITLE
|
|
>Debugging</TITLE
|
|
><META
|
|
NAME="GENERATOR"
|
|
CONTENT="Modular DocBook HTML Stylesheet Version 1.7"><LINK
|
|
REL="HOME"
|
|
TITLE="Advanced Bash-Scripting Guide"
|
|
HREF="index.html"><LINK
|
|
REL="UP"
|
|
TITLE="Advanced Topics"
|
|
HREF="part5.html"><LINK
|
|
REL="PREVIOUS"
|
|
TITLE="Of Zeros and Nulls"
|
|
HREF="zeros.html"><LINK
|
|
REL="NEXT"
|
|
TITLE="Options"
|
|
HREF="options.html"></HEAD
|
|
><BODY
|
|
CLASS="CHAPTER"
|
|
BGCOLOR="#FFFFFF"
|
|
TEXT="#000000"
|
|
LINK="#0000FF"
|
|
VLINK="#840084"
|
|
ALINK="#0000FF"
|
|
><DIV
|
|
CLASS="NAVHEADER"
|
|
><TABLE
|
|
SUMMARY="Header navigation table"
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
CELLPADDING="0"
|
|
CELLSPACING="0"
|
|
><TR
|
|
><TH
|
|
COLSPAN="3"
|
|
ALIGN="center"
|
|
>Advanced Bash-Scripting Guide: </TH
|
|
></TR
|
|
><TR
|
|
><TD
|
|
WIDTH="10%"
|
|
ALIGN="left"
|
|
VALIGN="bottom"
|
|
><A
|
|
HREF="zeros.html"
|
|
ACCESSKEY="P"
|
|
>Prev</A
|
|
></TD
|
|
><TD
|
|
WIDTH="80%"
|
|
ALIGN="center"
|
|
VALIGN="bottom"
|
|
></TD
|
|
><TD
|
|
WIDTH="10%"
|
|
ALIGN="right"
|
|
VALIGN="bottom"
|
|
><A
|
|
HREF="options.html"
|
|
ACCESSKEY="N"
|
|
>Next</A
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
><HR
|
|
ALIGN="LEFT"
|
|
WIDTH="100%"></DIV
|
|
><DIV
|
|
CLASS="CHAPTER"
|
|
><H1
|
|
><A
|
|
NAME="DEBUGGING"
|
|
></A
|
|
>Chapter 32. Debugging</H1
|
|
><TABLE
|
|
BORDER="0"
|
|
WIDTH="100%"
|
|
CELLSPACING="0"
|
|
CELLPADDING="0"
|
|
CLASS="EPIGRAPH"
|
|
><TR
|
|
><TD
|
|
WIDTH="45%"
|
|
> </TD
|
|
><TD
|
|
WIDTH="45%"
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
><I
|
|
><P
|
|
><I
|
|
>Debugging is twice as hard as writing the code in the first
|
|
place. Therefore, if you write the code as cleverly as possible,
|
|
you are, by definition, not smart enough to debug it.</I
|
|
></P
|
|
><P
|
|
><I
|
|
>--Brian Kernighan</I
|
|
></P
|
|
></I
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
><P
|
|
>The Bash shell contains no built-in debugger, and only bare-bones
|
|
debugging-specific commands and constructs. Syntax errors or
|
|
outright typos in the script generate cryptic error messages that
|
|
are often of no help in debugging a non-functional script.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="EX74"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 32-1. A buggy script</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# ex74.sh
|
|
|
|
# This is a buggy script.
|
|
# Where, oh where is the error?
|
|
|
|
a=37
|
|
|
|
if [$a -gt 27 ]
|
|
then
|
|
echo $a
|
|
fi
|
|
|
|
exit $? # 0! Why?</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>Output from script:
|
|
<TABLE
|
|
BORDER="1"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="SCREEN"
|
|
><TT
|
|
CLASS="COMPUTEROUTPUT"
|
|
>./ex74.sh: [37: command not found</TT
|
|
></PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
What's wrong with the above script? Hint: after the
|
|
<I
|
|
CLASS="FIRSTTERM"
|
|
>if</I
|
|
>.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="MISSINGKEYWORD"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 32-2. Missing <A
|
|
HREF="internal.html#KEYWORDREF"
|
|
>keyword</A
|
|
></B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# missing-keyword.sh
|
|
# What error message will this script generate? And why?
|
|
|
|
for a in 1 2 3
|
|
do
|
|
echo "$a"
|
|
# done # Required keyword 'done' commented out in line 8.
|
|
|
|
exit 0 # Will not exit here!
|
|
|
|
# === #
|
|
|
|
# From command line, after script terminates:
|
|
echo $? # 2</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>Output from script:
|
|
<TABLE
|
|
BORDER="1"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="SCREEN"
|
|
><TT
|
|
CLASS="COMPUTEROUTPUT"
|
|
>missing-keyword.sh: line 10: syntax error: unexpected end of file</TT
|
|
>
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
Note that the error message does <EM
|
|
>not</EM
|
|
> necessarily
|
|
reference the line in which the error occurs, but the line where the
|
|
Bash interpreter finally becomes aware of the error.
|
|
</P
|
|
><P
|
|
>Error messages may disregard comment lines in a script when
|
|
reporting the line number of a syntax error.</P
|
|
><P
|
|
>What if the script executes, but does not work as expected? This is the
|
|
all too familiar logic error.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="EX75"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 32-3. <I
|
|
CLASS="FIRSTTERM"
|
|
>test24</I
|
|
>: another buggy script</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
|
|
# This script is supposed to delete all filenames in current directory
|
|
#+ containing embedded spaces.
|
|
# It doesn't work.
|
|
# Why not?
|
|
|
|
|
|
badname=`ls | grep ' '`
|
|
|
|
# Try this:
|
|
# echo "$badname"
|
|
|
|
rm "$badname"
|
|
|
|
exit 0</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>Try to find out what's wrong with <A
|
|
HREF="debugging.html#EX75"
|
|
>Example 32-3</A
|
|
>
|
|
by uncommenting the <TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>echo "$badname"</B
|
|
></TT
|
|
> line. Echo
|
|
statements are useful for seeing whether what you expect is
|
|
actually what you get.</P
|
|
><P
|
|
>In this particular case, <TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>rm "$badname"</B
|
|
></TT
|
|
>
|
|
will not give the desired results because
|
|
<TT
|
|
CLASS="VARNAME"
|
|
>$badname</TT
|
|
> should not be quoted. Placing it
|
|
in quotes ensures that <B
|
|
CLASS="COMMAND"
|
|
>rm</B
|
|
> has only one
|
|
argument (it will match only one filename). A partial fix
|
|
is to remove to quotes from <TT
|
|
CLASS="VARNAME"
|
|
>$badname</TT
|
|
> and
|
|
to reset <TT
|
|
CLASS="VARNAME"
|
|
>$IFS</TT
|
|
> to contain only a newline,
|
|
<TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>IFS=$'\n'</B
|
|
></TT
|
|
>. However, there are simpler
|
|
ways of going about it.
|
|
<TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
># Correct methods of deleting filenames containing spaces.
|
|
rm *\ *
|
|
rm *" "*
|
|
rm *' '*
|
|
# Thank you. S.C.</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
|
|
</P
|
|
><P
|
|
>Summarizing the symptoms of a buggy script,
|
|
<P
|
|
></P
|
|
><OL
|
|
TYPE="1"
|
|
><LI
|
|
><P
|
|
>It bombs with a <SPAN
|
|
CLASS="QUOTE"
|
|
>"<SPAN
|
|
CLASS="ERRORNAME"
|
|
>syntax error</SPAN
|
|
>"</SPAN
|
|
> message, or</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
>It runs, but does not work as expected
|
|
(<SPAN
|
|
CLASS="ERRORNAME"
|
|
>logic error</SPAN
|
|
>).</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
>It runs, works as expected, but has nasty side effects
|
|
(<SPAN
|
|
CLASS="ERRORNAME"
|
|
>logic bomb</SPAN
|
|
>).</P
|
|
></LI
|
|
></OL
|
|
>
|
|
</P
|
|
><P
|
|
><A
|
|
NAME="DEBUGTOOLS"
|
|
></A
|
|
></P
|
|
><P
|
|
>Tools for debugging non-working scripts include
|
|
<P
|
|
></P
|
|
><OL
|
|
TYPE="1"
|
|
><LI
|
|
><P
|
|
>Inserting <A
|
|
HREF="internal.html#ECHOREF"
|
|
>echo</A
|
|
>
|
|
statements at critical points in the script to trace the
|
|
variables, and otherwise give a snapshot of what is going
|
|
on.</P
|
|
><DIV
|
|
CLASS="TIP"
|
|
><P
|
|
></P
|
|
><TABLE
|
|
CLASS="TIP"
|
|
WIDTH="90%"
|
|
BORDER="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="25"
|
|
ALIGN="CENTER"
|
|
VALIGN="TOP"
|
|
><IMG
|
|
SRC="../images/tip.gif"
|
|
HSPACE="5"
|
|
ALT="Tip"></TD
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
><P
|
|
>Even better is an <B
|
|
CLASS="COMMAND"
|
|
>echo</B
|
|
> that echoes
|
|
only when <I
|
|
CLASS="FIRSTTERM"
|
|
>debug</I
|
|
> is on.</P
|
|
><P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>### debecho (debug-echo), by Stefano Falsetto ###
|
|
### Will echo passed parameters only if DEBUG is set to a value. ###
|
|
debecho () {
|
|
if [ ! -z "$DEBUG" ]; then
|
|
echo "$1" >&2
|
|
# ^^^ to stderr
|
|
fi
|
|
}
|
|
|
|
DEBUG=on
|
|
Whatever=whatnot
|
|
debecho $Whatever # whatnot
|
|
|
|
DEBUG=
|
|
Whatever=notwhat
|
|
debecho $Whatever # (Will not echo.)</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></P
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
></LI
|
|
><LI
|
|
><P
|
|
>Using the <A
|
|
HREF="extmisc.html#TEEREF"
|
|
>tee</A
|
|
> filter
|
|
to check processes or data flows at critical points.</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
>Setting option flags <TT
|
|
CLASS="OPTION"
|
|
>-n -v -x</TT
|
|
></P
|
|
><P
|
|
><TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>sh -n scriptname</B
|
|
></TT
|
|
> checks for
|
|
syntax errors without actually running the script. This is
|
|
the equivalent of inserting <TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>set -n</B
|
|
></TT
|
|
> or
|
|
<TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>set -o noexec</B
|
|
></TT
|
|
> into the script. Note
|
|
that certain types of syntax errors can slip past this
|
|
check.</P
|
|
><P
|
|
><TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>sh -v scriptname</B
|
|
></TT
|
|
> echoes each
|
|
command before executing it. This is the equivalent of
|
|
inserting <TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>set -v</B
|
|
></TT
|
|
> or <TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>set
|
|
-o verbose</B
|
|
></TT
|
|
> in the script.</P
|
|
><P
|
|
>The <TT
|
|
CLASS="OPTION"
|
|
>-n</TT
|
|
> and <TT
|
|
CLASS="OPTION"
|
|
>-v</TT
|
|
>
|
|
flags work well together. <TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>sh -nv
|
|
scriptname</B
|
|
></TT
|
|
> gives a verbose syntax check.</P
|
|
><P
|
|
><TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>sh -x scriptname</B
|
|
></TT
|
|
> echoes the result each
|
|
command, but in an abbreviated manner. This is the equivalent of
|
|
inserting <TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>set -x</B
|
|
></TT
|
|
> or
|
|
<TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>set -o xtrace</B
|
|
></TT
|
|
> in the script.</P
|
|
><P
|
|
><A
|
|
NAME="UNDVARERR"
|
|
></A
|
|
></P
|
|
><P
|
|
>Inserting <TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>set -u</B
|
|
></TT
|
|
> or
|
|
<TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>set -o nounset</B
|
|
></TT
|
|
> in the script runs it, but
|
|
gives an <SPAN
|
|
CLASS="ERRORNAME"
|
|
>unbound variable</SPAN
|
|
> error message
|
|
and aborts the script.
|
|
<TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>set -u # Or set -o nounset
|
|
|
|
# Setting a variable to null will not trigger the error/abort.
|
|
# unset_var=
|
|
|
|
echo $unset_var # Unset (and undeclared) variable.
|
|
|
|
echo "Should not echo!"
|
|
|
|
# sh t2.sh
|
|
# t2.sh: line 6: unset_var: unbound variable</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></P
|
|
></LI
|
|
><LI
|
|
><P
|
|
>Using an <SPAN
|
|
CLASS="QUOTE"
|
|
>"assert"</SPAN
|
|
> function to test a
|
|
variable or condition at critical points in a script. (This is
|
|
an idea borrowed from C.)</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="ASSERT"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 32-4. Testing a condition with an
|
|
<I
|
|
CLASS="FIRSTTERM"
|
|
>assert</I
|
|
></B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# assert.sh
|
|
|
|
#######################################################################
|
|
assert () # If condition false,
|
|
{ #+ exit from script
|
|
#+ with appropriate error message.
|
|
E_PARAM_ERR=98
|
|
E_ASSERT_FAILED=99
|
|
|
|
|
|
if [ -z "$2" ] # Not enough parameters passed
|
|
then #+ to assert() function.
|
|
return $E_PARAM_ERR # No damage done.
|
|
fi
|
|
|
|
lineno=$2
|
|
|
|
if [ ! $1 ]
|
|
then
|
|
echo "Assertion failed: \"$1\""
|
|
echo "File \"$0\", line $lineno" # Give name of file and line number.
|
|
exit $E_ASSERT_FAILED
|
|
# else
|
|
# return
|
|
# and continue executing the script.
|
|
fi
|
|
} # Insert a similar assert() function into a script you need to debug.
|
|
#######################################################################
|
|
|
|
|
|
a=5
|
|
b=4
|
|
condition="$a -lt $b" # Error message and exit from script.
|
|
# Try setting "condition" to something else
|
|
#+ and see what happens.
|
|
|
|
assert "$condition" $LINENO
|
|
# The remainder of the script executes only if the "assert" does not fail.
|
|
|
|
|
|
# Some commands.
|
|
# Some more commands . . .
|
|
echo "This statement echoes only if the \"assert\" does not fail."
|
|
# . . .
|
|
# More commands . . .
|
|
|
|
exit $?</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
></LI
|
|
><LI
|
|
><P
|
|
>Using the <A
|
|
HREF="internalvariables.html#LINENOREF"
|
|
>$LINENO</A
|
|
>
|
|
variable and the <A
|
|
HREF="internal.html#CALLERREF"
|
|
>caller</A
|
|
>
|
|
builtin.</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
><A
|
|
NAME="DEBUGTRAP"
|
|
></A
|
|
>Trapping at exit.</P
|
|
><P
|
|
>The <A
|
|
HREF="internal.html#EXITREF"
|
|
>exit</A
|
|
> command in a script
|
|
triggers a signal <SPAN
|
|
CLASS="RETURNVALUE"
|
|
>0</SPAN
|
|
>, terminating
|
|
the process, that is, the script itself.
|
|
|
|
<A
|
|
NAME="AEN19460"
|
|
HREF="#FTN.AEN19460"
|
|
><SPAN
|
|
CLASS="footnote"
|
|
>[1]</SPAN
|
|
></A
|
|
>
|
|
|
|
It is often useful to trap the
|
|
<I
|
|
CLASS="FIRSTTERM"
|
|
>exit</I
|
|
>, forcing a <SPAN
|
|
CLASS="QUOTE"
|
|
>"printout"</SPAN
|
|
>
|
|
of variables, for example. The <I
|
|
CLASS="FIRSTTERM"
|
|
>trap</I
|
|
>
|
|
must be the first command in the script.</P
|
|
></LI
|
|
></OL
|
|
>
|
|
</P
|
|
><P
|
|
></P
|
|
><DIV
|
|
CLASS="VARIABLELIST"
|
|
><P
|
|
><B
|
|
><A
|
|
NAME="TRAPREF1"
|
|
></A
|
|
>Trapping signals</B
|
|
></P
|
|
><DL
|
|
><DT
|
|
><B
|
|
CLASS="COMMAND"
|
|
>trap</B
|
|
></DT
|
|
><DD
|
|
><P
|
|
>Specifies an action on receipt of a
|
|
signal; also useful for debugging.</P
|
|
><P
|
|
><A
|
|
NAME="SIGNALD"
|
|
></A
|
|
></P
|
|
><TABLE
|
|
CLASS="SIDEBAR"
|
|
BORDER="1"
|
|
CELLPADDING="5"
|
|
><TR
|
|
><TD
|
|
><DIV
|
|
CLASS="SIDEBAR"
|
|
><A
|
|
NAME="AEN19477"
|
|
></A
|
|
><P
|
|
></P
|
|
><P
|
|
>A <I
|
|
CLASS="FIRSTTERM"
|
|
>signal</I
|
|
> is a message
|
|
sent to a process, either by the kernel or another
|
|
process, telling it to take some specified action
|
|
(usually to terminate). For example, hitting a
|
|
<A
|
|
HREF="special-chars.html#CTLCREF"
|
|
>Control-C</A
|
|
>
|
|
sends a user interrupt, an INT signal, to a running
|
|
program.</P
|
|
><P
|
|
></P
|
|
></DIV
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
><P
|
|
><EM
|
|
>A simple instance:</EM
|
|
>
|
|
<TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="90%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>trap '' 2
|
|
# Ignore interrupt 2 (Control-C), with no action specified.
|
|
|
|
trap 'echo "Control-C disabled."' 2
|
|
# Message when Control-C pressed.</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
></DD
|
|
></DL
|
|
></DIV
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="EX76"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 32-5. Trapping at exit</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# Hunting variables with a trap.
|
|
|
|
trap 'echo Variable Listing --- a = $a b = $b' EXIT
|
|
# EXIT is the name of the signal generated upon exit from a script.
|
|
#
|
|
# The command specified by the "trap" doesn't execute until
|
|
#+ the appropriate signal is sent.
|
|
|
|
echo "This prints before the \"trap\" --"
|
|
echo "even though the script sees the \"trap\" first."
|
|
echo
|
|
|
|
a=39
|
|
|
|
b=36
|
|
|
|
exit 0
|
|
# Note that commenting out the 'exit' command makes no difference,
|
|
#+ since the script exits in any case after running out of commands.</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="ONLINE"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 32-6. Cleaning up after <B
|
|
CLASS="KEYCAP"
|
|
>Control-C</B
|
|
></B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# logon.sh: A quick 'n dirty script to check whether you are on-line yet.
|
|
|
|
umask 177 # Make sure temp files are not world readable.
|
|
|
|
|
|
TRUE=1
|
|
LOGFILE=/var/log/messages
|
|
# Note that $LOGFILE must be readable
|
|
#+ (as root, chmod 644 /var/log/messages).
|
|
TEMPFILE=temp.$$
|
|
# Create a "unique" temp file name, using process id of the script.
|
|
# Using 'mktemp' is an alternative.
|
|
# For example:
|
|
# TEMPFILE=`mktemp temp.XXXXXX`
|
|
KEYWORD=address
|
|
# At logon, the line "remote IP address xxx.xxx.xxx.xxx"
|
|
# appended to /var/log/messages.
|
|
ONLINE=22
|
|
USER_INTERRUPT=13
|
|
CHECK_LINES=100
|
|
# How many lines in log file to check.
|
|
|
|
trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT
|
|
# Cleans up the temp file if script interrupted by control-c.
|
|
|
|
echo
|
|
|
|
while [ $TRUE ] #Endless loop.
|
|
do
|
|
tail -n $CHECK_LINES $LOGFILE> $TEMPFILE
|
|
# Saves last 100 lines of system log file as temp file.
|
|
# Necessary, since newer kernels generate many log messages at log on.
|
|
search=`grep $KEYWORD $TEMPFILE`
|
|
# Checks for presence of the "IP address" phrase,
|
|
#+ indicating a successful logon.
|
|
|
|
if [ ! -z "$search" ] # Quotes necessary because of possible spaces.
|
|
then
|
|
echo "On-line"
|
|
rm -f $TEMPFILE # Clean up temp file.
|
|
exit $ONLINE
|
|
else
|
|
echo -n "." # The -n option to echo suppresses newline,
|
|
#+ so you get continuous rows of dots.
|
|
fi
|
|
|
|
sleep 1
|
|
done
|
|
|
|
|
|
# Note: if you change the KEYWORD variable to "Exit",
|
|
#+ this script can be used while on-line
|
|
#+ to check for an unexpected logoff.
|
|
|
|
# Exercise: Change the script, per the above note,
|
|
# and prettify it.
|
|
|
|
exit 0
|
|
|
|
|
|
# Nick Drage suggests an alternate method:
|
|
|
|
while true
|
|
do ifconfig ppp0 | grep UP 1> /dev/null && echo "connected" && exit 0
|
|
echo -n "." # Prints dots (.....) until connected.
|
|
sleep 2
|
|
done
|
|
|
|
# Problem: Hitting Control-C to terminate this process may be insufficient.
|
|
#+ (Dots may keep on echoing.)
|
|
# Exercise: Fix this.
|
|
|
|
|
|
|
|
# Stephane Chazelas has yet another alternative:
|
|
|
|
CHECK_INTERVAL=1
|
|
|
|
while ! tail -n 1 "$LOGFILE" | grep -q "$KEYWORD"
|
|
do echo -n .
|
|
sleep $CHECK_INTERVAL
|
|
done
|
|
echo "On-line"
|
|
|
|
# Exercise: Discuss the relative strengths and weaknesses
|
|
# of each of these various approaches.</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="PROGRESSBAR2"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 32-7. A Simple Implementation of a Progress Bar</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#! /bin/bash
|
|
# progress-bar2.sh
|
|
# Author: Graham Ewart (with reformatting by ABS Guide author).
|
|
# Used in ABS Guide with permission (thanks!).
|
|
|
|
# Invoke this script with bash. It doesn't work with sh.
|
|
|
|
interval=1
|
|
long_interval=10
|
|
|
|
{
|
|
trap "exit" SIGUSR1
|
|
sleep $interval; sleep $interval
|
|
while true
|
|
do
|
|
echo -n '.' # Use dots.
|
|
sleep $interval
|
|
done; } & # Start a progress bar as a background process.
|
|
|
|
pid=$!
|
|
trap "echo !; kill -USR1 $pid; wait $pid" EXIT # To handle ^C.
|
|
|
|
echo -n 'Long-running process '
|
|
sleep $long_interval
|
|
echo ' Finished!'
|
|
|
|
kill -USR1 $pid
|
|
wait $pid # Stop the progress bar.
|
|
trap EXIT
|
|
|
|
exit $?</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><DIV
|
|
CLASS="NOTE"
|
|
><P
|
|
></P
|
|
><TABLE
|
|
CLASS="NOTE"
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="25"
|
|
ALIGN="CENTER"
|
|
VALIGN="TOP"
|
|
><IMG
|
|
SRC="../images/note.gif"
|
|
HSPACE="5"
|
|
ALT="Note"></TD
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
><P
|
|
>The <TT
|
|
CLASS="OPTION"
|
|
>DEBUG</TT
|
|
> argument to
|
|
<B
|
|
CLASS="COMMAND"
|
|
>trap</B
|
|
> causes a specified action to execute
|
|
after every command in a script. This permits tracing variables,
|
|
for example.
|
|
|
|
<DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="VARTRACE"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 32-8. Tracing a variable</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
|
|
trap 'echo "VARIABLE-TRACE> \$variable = \"$variable\""' DEBUG
|
|
# Echoes the value of $variable after every command.
|
|
|
|
variable=29; line=$LINENO
|
|
|
|
echo " Just initialized \$variable to $variable in line number $line."
|
|
|
|
let "variable *= 3"; line=$LINENO
|
|
echo " Just multiplied \$variable by 3 in line number $line."
|
|
|
|
exit 0
|
|
|
|
# The "trap 'command1 . . . command2 . . .' DEBUG" construct is
|
|
#+ more appropriate in the context of a complex script,
|
|
#+ where inserting multiple "echo $variable" statements might be
|
|
#+ awkward and time-consuming.
|
|
|
|
# Thanks, Stephane Chazelas for the pointer.
|
|
|
|
|
|
Output of script:
|
|
|
|
VARIABLE-TRACE> $variable = ""
|
|
VARIABLE-TRACE> $variable = "29"
|
|
Just initialized $variable to 29.
|
|
VARIABLE-TRACE> $variable = "29"
|
|
VARIABLE-TRACE> $variable = "87"
|
|
Just multiplied $variable by 3.
|
|
VARIABLE-TRACE> $variable = "87"</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
>
|
|
|
|
</P
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
>Of course, the <B
|
|
CLASS="COMMAND"
|
|
>trap</B
|
|
> command has other uses
|
|
aside from debugging, such as disabling certain keystrokes within a
|
|
script (see <A
|
|
HREF="contributed-scripts.html#STOPWATCH"
|
|
>Example A-43</A
|
|
>).</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="MULTIPLEPROC"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 32-9. Running multiple processes (on an SMP box)</B
|
|
></P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>#!/bin/bash
|
|
# parent.sh
|
|
# Running multiple processes on an SMP box.
|
|
# Author: Tedman Eng
|
|
|
|
# This is the first of two scripts,
|
|
#+ both of which must be present in the current working directory.
|
|
|
|
|
|
|
|
|
|
LIMIT=$1 # Total number of process to start
|
|
NUMPROC=4 # Number of concurrent threads (forks?)
|
|
PROCID=1 # Starting Process ID
|
|
echo "My PID is $$"
|
|
|
|
function start_thread() {
|
|
if [ $PROCID -le $LIMIT ] ; then
|
|
./child.sh $PROCID&
|
|
let "PROCID++"
|
|
else
|
|
echo "Limit reached."
|
|
wait
|
|
exit
|
|
fi
|
|
}
|
|
|
|
while [ "$NUMPROC" -gt 0 ]; do
|
|
start_thread;
|
|
let "NUMPROC--"
|
|
done
|
|
|
|
|
|
while true
|
|
do
|
|
|
|
trap "start_thread" SIGRTMIN
|
|
|
|
done
|
|
|
|
exit 0
|
|
|
|
|
|
|
|
# ======== Second script follows ========
|
|
|
|
|
|
#!/bin/bash
|
|
# child.sh
|
|
# Running multiple processes on an SMP box.
|
|
# This script is called by parent.sh.
|
|
# Author: Tedman Eng
|
|
|
|
temp=$RANDOM
|
|
index=$1
|
|
shift
|
|
let "temp %= 5"
|
|
let "temp += 4"
|
|
echo "Starting $index Time:$temp" "$@"
|
|
sleep ${temp}
|
|
echo "Ending $index"
|
|
kill -s SIGRTMIN $PPID
|
|
|
|
exit 0
|
|
|
|
|
|
# ======================= SCRIPT AUTHOR'S NOTES ======================= #
|
|
# It's not completely bug free.
|
|
# I ran it with limit = 500 and after the first few hundred iterations,
|
|
#+ one of the concurrent threads disappeared!
|
|
# Not sure if this is collisions from trap signals or something else.
|
|
# Once the trap is received, there's a brief moment while executing the
|
|
#+ trap handler but before the next trap is set. During this time, it may
|
|
#+ be possible to miss a trap signal, thus miss spawning a child process.
|
|
|
|
# No doubt someone may spot the bug and will be writing
|
|
#+ . . . in the future.
|
|
|
|
|
|
|
|
# ===================================================================== #
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------#
|
|
|
|
|
|
|
|
#################################################################
|
|
# The following is the original script written by Vernia Damiano.
|
|
# Unfortunately, it doesn't work properly.
|
|
#################################################################
|
|
|
|
#!/bin/bash
|
|
|
|
# Must call script with at least one integer parameter
|
|
#+ (number of concurrent processes).
|
|
# All other parameters are passed through to the processes started.
|
|
|
|
|
|
INDICE=8 # Total number of process to start
|
|
TEMPO=5 # Maximum sleep time per process
|
|
E_BADARGS=65 # No arg(s) passed to script.
|
|
|
|
if [ $# -eq 0 ] # Check for at least one argument passed to script.
|
|
then
|
|
echo "Usage: `basename $0` number_of_processes [passed params]"
|
|
exit $E_BADARGS
|
|
fi
|
|
|
|
NUMPROC=$1 # Number of concurrent process
|
|
shift
|
|
PARAMETRI=( "$@" ) # Parameters of each process
|
|
|
|
function avvia() {
|
|
local temp
|
|
local index
|
|
temp=$RANDOM
|
|
index=$1
|
|
shift
|
|
let "temp %= $TEMPO"
|
|
let "temp += 1"
|
|
echo "Starting $index Time:$temp" "$@"
|
|
sleep ${temp}
|
|
echo "Ending $index"
|
|
kill -s SIGRTMIN $$
|
|
}
|
|
|
|
function parti() {
|
|
if [ $INDICE -gt 0 ] ; then
|
|
avvia $INDICE "${PARAMETRI[@]}" &
|
|
let "INDICE--"
|
|
else
|
|
trap : SIGRTMIN
|
|
fi
|
|
}
|
|
|
|
trap parti SIGRTMIN
|
|
|
|
while [ "$NUMPROC" -gt 0 ]; do
|
|
parti;
|
|
let "NUMPROC--"
|
|
done
|
|
|
|
wait
|
|
trap - SIGRTMIN
|
|
|
|
exit $?
|
|
|
|
: <<SCRIPT_AUTHOR_COMMENTS
|
|
I had the need to run a program, with specified options, on a number of
|
|
different files, using a SMP machine. So I thought [I'd] keep running
|
|
a specified number of processes and start a new one each time . . . one
|
|
of these terminates.
|
|
|
|
The "wait" instruction does not help, since it waits for a given process
|
|
or *all* process started in background. So I wrote [this] bash script
|
|
that can do the job, using the "trap" instruction.
|
|
--Vernia Damiano
|
|
SCRIPT_AUTHOR_COMMENTS</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><DIV
|
|
CLASS="NOTE"
|
|
><P
|
|
></P
|
|
><TABLE
|
|
CLASS="NOTE"
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="25"
|
|
ALIGN="CENTER"
|
|
VALIGN="TOP"
|
|
><IMG
|
|
SRC="../images/note.gif"
|
|
HSPACE="5"
|
|
ALT="Note"></TD
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
><P
|
|
><TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>trap '' SIGNAL</B
|
|
></TT
|
|
> (two adjacent
|
|
apostrophes) disables SIGNAL for the remainder of the
|
|
script. <TT
|
|
CLASS="USERINPUT"
|
|
><B
|
|
>trap SIGNAL</B
|
|
></TT
|
|
> restores
|
|
the functioning of SIGNAL once more. This is useful to
|
|
protect a critical portion of a script from an undesirable
|
|
interrupt.</P
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><P
|
|
><TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
> trap '' 2 # Signal 2 is Control-C, now disabled.
|
|
command
|
|
command
|
|
command
|
|
trap 2 # Reenables Control-C
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></P
|
|
><TABLE
|
|
CLASS="SIDEBAR"
|
|
BORDER="1"
|
|
CELLPADDING="5"
|
|
><TR
|
|
><TD
|
|
><DIV
|
|
CLASS="SIDEBAR"
|
|
><A
|
|
NAME="AEN19513"
|
|
></A
|
|
><P
|
|
></P
|
|
><P
|
|
><A
|
|
HREF="bashver3.html#BASH3REF"
|
|
>Version 3</A
|
|
> of Bash adds the
|
|
following <A
|
|
HREF="internalvariables.html#INTERNALVARIABLES1"
|
|
>internal
|
|
variables</A
|
|
> for use by the debugger.
|
|
|
|
<P
|
|
></P
|
|
><OL
|
|
TYPE="1"
|
|
><LI
|
|
><P
|
|
><TT
|
|
CLASS="VARNAME"
|
|
>$BASH_ARGC</TT
|
|
></P
|
|
><P
|
|
>Number of command-line arguments passed to script,
|
|
similar to <A
|
|
HREF="internalvariables.html#CLACOUNTREF"
|
|
><TT
|
|
CLASS="VARNAME"
|
|
>$#</TT
|
|
></A
|
|
>.</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
><TT
|
|
CLASS="VARNAME"
|
|
>$BASH_ARGV</TT
|
|
></P
|
|
><P
|
|
>Final command-line parameter passed to script, equivalent
|
|
<A
|
|
HREF="othertypesv.html#LASTARGREF"
|
|
><TT
|
|
CLASS="VARNAME"
|
|
>${!#}</TT
|
|
></A
|
|
>.</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
><TT
|
|
CLASS="VARNAME"
|
|
>$BASH_COMMAND</TT
|
|
></P
|
|
><P
|
|
>Command currently executing.</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
><TT
|
|
CLASS="VARNAME"
|
|
>$BASH_EXECUTION_STRING</TT
|
|
></P
|
|
><P
|
|
>The <I
|
|
CLASS="FIRSTTERM"
|
|
>option string</I
|
|
> following the
|
|
<TT
|
|
CLASS="OPTION"
|
|
>-c</TT
|
|
> <A
|
|
HREF="bash-options.html#CLOPTS"
|
|
>option</A
|
|
>
|
|
to Bash.</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
><TT
|
|
CLASS="VARNAME"
|
|
>$BASH_LINENO</TT
|
|
></P
|
|
><P
|
|
>In a <A
|
|
HREF="functions.html#FUNCTIONREF"
|
|
>function</A
|
|
>,
|
|
indicates the line number of the function call.</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
><TT
|
|
CLASS="VARNAME"
|
|
>$BASH_REMATCH</TT
|
|
></P
|
|
><P
|
|
>Array variable associated with <B
|
|
CLASS="COMMAND"
|
|
>=~</B
|
|
>
|
|
<A
|
|
HREF="bashver3.html#REGEXMATCHREF"
|
|
>conditional regex
|
|
matching</A
|
|
>.</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
><A
|
|
NAME="BASHSOURCEREF"
|
|
></A
|
|
></P
|
|
><P
|
|
><TT
|
|
CLASS="VARNAME"
|
|
>$BASH_SOURCE</TT
|
|
></P
|
|
><P
|
|
>This is the name of the script, usually the same as
|
|
<A
|
|
HREF="othertypesv.html#ARG0"
|
|
>$0</A
|
|
>.</P
|
|
></LI
|
|
><LI
|
|
><P
|
|
> <A
|
|
HREF="internalvariables.html#BASHSUBSHELLREF"
|
|
><TT
|
|
CLASS="VARNAME"
|
|
>$BASH_SUBSHELL</TT
|
|
></A
|
|
></P
|
|
></LI
|
|
></OL
|
|
></P
|
|
><P
|
|
></P
|
|
></DIV
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
><H3
|
|
CLASS="FOOTNOTES"
|
|
>Notes</H3
|
|
><TABLE
|
|
BORDER="0"
|
|
CLASS="FOOTNOTES"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
WIDTH="5%"
|
|
><A
|
|
NAME="FTN.AEN19460"
|
|
HREF="debugging.html#AEN19460"
|
|
><SPAN
|
|
CLASS="footnote"
|
|
>[1]</SPAN
|
|
></A
|
|
></TD
|
|
><TD
|
|
ALIGN="LEFT"
|
|
VALIGN="TOP"
|
|
WIDTH="95%"
|
|
><P
|
|
>By convention, <TT
|
|
CLASS="REPLACEABLE"
|
|
><I
|
|
>signal
|
|
0</I
|
|
></TT
|
|
> is assigned to <A
|
|
HREF="exit-status.html#EXITCOMMANDREF"
|
|
>exit</A
|
|
>. </P
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
><DIV
|
|
CLASS="NAVFOOTER"
|
|
><HR
|
|
ALIGN="LEFT"
|
|
WIDTH="100%"><TABLE
|
|
SUMMARY="Footer navigation table"
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
CELLPADDING="0"
|
|
CELLSPACING="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="left"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="zeros.html"
|
|
ACCESSKEY="P"
|
|
>Prev</A
|
|
></TD
|
|
><TD
|
|
WIDTH="34%"
|
|
ALIGN="center"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="index.html"
|
|
ACCESSKEY="H"
|
|
>Home</A
|
|
></TD
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="right"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="options.html"
|
|
ACCESSKEY="N"
|
|
>Next</A
|
|
></TD
|
|
></TR
|
|
><TR
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="left"
|
|
VALIGN="top"
|
|
>Of Zeros and Nulls</TD
|
|
><TD
|
|
WIDTH="34%"
|
|
ALIGN="center"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="part5.html"
|
|
ACCESSKEY="U"
|
|
>Up</A
|
|
></TD
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="right"
|
|
VALIGN="top"
|
|
>Options</TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
></BODY
|
|
></HTML
|
|
> |