Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Cooper M.Advanced bash-scripting guide.2002.pdf
Скачиваний:
15
Добавлен:
23.08.2013
Размер:
916.67 Кб
Скачать

Chapter 29. Of Zeros and Nulls

/dev/zero and /dev/null

Uses of /dev/null

Think of /dev/null as a "black hole". It is the nearest equivalent to a write−only file. Everything written to it disappears forever. Attempts to read or output from it result in nothing. Nevertheless, /dev/null can be quite useful from both the command line and in scripts.

Suppressing stdout or stderr (from Example 12−2):

rm $badname 2>/dev/null

#So error messages [stderr] deep−sixed.

Deleting contents of a file, but preserving the file itself, with all attendant permissions (from Example 2−1 and Example 2−2):

cat /dev/null > /var/log/messages

# : > /var/log/messages has same effect, but does not spawn a new process.

cat /dev/null > /var/log/wtmp

Automatically emptying the contents of a logfile (especially good for dealing with those nasty "cookies" sent by Web commercial sites):

Example 29−1. Hiding the cookie jar

if [ −f ~/.netscape/cookies ] # Remove, if exists. then

rm −f ~/.netscape/cookies

fi

ln −s /dev/null ~/.netscape/cookies

# All cookies now get sent to a black hole, rather than saved to disk.

Uses of /dev/zero

Like /dev/null, /dev/zero is a pseudo file, but it actually contains nulls (numerical zeros, not the ASCII kind). Output written to it disappears, and it is fairly difficult to actually read the nulls in /dev/zero, though it can be done with od or a hex editor. The chief use for /dev/zero is in creating an initialized dummy file of specified length intended as a temporary swap file.

Example 29−2. Setting up a swapfile using /dev/zero

#!/bin/bash

#Creating a swapfile.

#This script must be run as root.

ROOT_UID=0

# Root has $UID 0.

Chapter 29. Of Zeros and Nulls

280

Advanced Bash−Scripting Guide

E_WRONG_USER=65

# Not root?

FILE=/swap

BLOCKSIZE=1024

MINBLOCKS=40

SUCCESS=0

if [ "$UID" −ne "$ROOT_UID" ] then

echo; echo "You must be root to run this script."; echo exit $E_WRONG_USER

fi

if [ −n "$1" ]

 

 

then

 

 

blocks=$1

 

 

else

 

 

blocks=$MINBLOCKS

# Set to default of 40 blocks

fi

# if nothing specified on command line.

if [ "$blocks" −lt $MINBLOCKS ]

 

 

then

 

 

blocks=$MINBLOCKS

# Must be at least 40

blocks long.

fi

 

 

echo "Creating swap file of size $blocks blocks (KB)."

 

dd if=/dev/zero of=$FILE bs=$BLOCKSIZE count=$blocks

# Zero out file.

mkswap $FILE $blocks

# Designate it a swap file.

swapon $FILE

# Activate swap file.

 

echo "Swap file created and activated."

exit $SUCCESS

Another application of /dev/zero is to "zero out" a file of a designated size for a special purpose, such as mounting a filesystem on a loopback device (see Example 13−6) or securely deleting a file (see Example 12−34).

Example 29−3. Creating a ramdisk

#!/bin/bash

#ramdisk.sh

#A "ramdisk" is a segment of system RAM memory #+ that acts as if it were a filesystem.

#Its advantage is very fast access (read/write time).

#Disadvantages: volatility, loss of data on reboot or powerdown.

#

less RAM available to system.

#

 

#What good is a ramdisk?

#Keeping a large dataset, such as a table or dictionary on ramdisk

#+ speeds up data lookup, since memory access is much faster than disk access.

E_NON_ROOT_USER=70

# Must run as root.

ROOTUSER_NAME=root

 

Chapter 29. Of Zeros and Nulls

281

Advanced Bash−Scripting Guide

MOUNTPT=/mnt/ramdisk

 

 

 

 

SIZE=2000

# 2K

blocks

(change as appropriate)

BLOCKSIZE=1024

#

1K

(1024 byte) block size

DEVICE=/dev/ram0

#

First ram

device

username=`id −nu`

if [ "$username" != "$ROOTUSER_NAME" ] then

echo "Must be root to run \"`basename $0`\"." exit $E_NON_ROOT_USER

fi

if [ ! −d "$MOUNTPT" ]

#

Test whether mount point already there,

then

#+

so no error if this script is run

mkdir $MOUNTPT

#+ multiple times.

fi

 

 

dd if=/dev/zero of=$DEVICE count=$SIZE bs=$BLOCKSIZE # Zero out RAM device.

mke2fs $DEVICE

# Create an ext2 filesystem on it.

mount $DEVICE $MOUNTPT

# Mount it.

chmod 777 $MOUNTPT

#

So ordinary user can access ramdisk.

 

#

However, must be root to unmount it.

echo "\"$MOUNTPT\" now available for use."

#The ramdisk is now accessible for storing files, even by an ordinary user.

#Caution, the ramdisk is volatile, and its contents will disappear

#+ on reboot or power loss.

#Copy anything you want saved to a regular directory.

#After reboot, run this script again to set up ramdisk.

#Remounting /mnt/ramdisk without the other steps will not work.

exit 0

Chapter 29. Of Zeros and Nulls

282

Chapter 30. Debugging

The Bash shell contains no debugger, nor even any debugging−specific commands or 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.

Example 30−1. A buggy script

#!/bin/bash

#ex74.sh

#This is a buggy script.

a=37

if [$a −gt 27 ] then

echo $a

fi

exit 0

Output from script:

./ex74.sh: [37: command not found

What's wrong with the above script (hint: after the if)?

What if the script executes, but does not work as expected? This is the all too familiar logic error.

Example 30−2. test24, another buggy script

#!/bin/bash

#This is supposed to delete all filenames in current directory #+ containing embedded spaces.

#It doesn't work. Why not?

badname=`ls | grep ' '`

# echo "$badname"

rm "$badname"

exit 0

Try to find out what's wrong with Example 30−2 by uncommenting the echo "$badname" line. Echo statements are useful for seeing whether what you expect is actually what you get.

In this particular case, rm "$badname" will not give the desired results because $badname should not be quoted. Placing it in quotes ensures that rm has only one argument (it will match only one filename). A

Chapter 30. Debugging

283

Advanced Bash−Scripting Guide

partial fix is to remove to quotes from $badname and to reset $IFS to contain only a newline, IFS=$'\n'. However, there are simpler ways of going about it.

#Correct methods of deleting filenames containing spaces. rm *\ *

rm *" "* rm *' '*

#Thank you. S.C.

Summarizing the symptoms of a buggy script,

1.It bombs with an error message syntax error, or

2.It runs, but does not work as expected (logic error)

3.It runs, works as expected, but has nasty side effects (logic bomb).

Tools for debugging non−working scripts include

1.echo statements at critical points in the script to trace the variables, and otherwise give a snapshot of what is going on.

2.using the tee filter to check processes or data flows at critical points.

3.setting option flags −n −v −x

sh −n scriptname checks for syntax errors without actually running the script. This is the equivalent of inserting set −n or set −o noexec into the script. Note that certain types of syntax errors can slip past this check.

sh −v scriptname echoes each command before executing it. This is the equivalent of inserting set −v or set −o verbose in the script.

The −n and −v flags work well together. sh −nv scriptname gives a verbose syntax check.

sh −x scriptname echoes the result each command, but in an abbreviated manner. This is the equivalent of inserting set −x or set −o xtrace in the script.

Inserting set −u or set −o nounset in the script runs it, but gives an unbound variable error message at each attempt to use an undeclared variable.

4.Using an "assert" function to test a variable or condition at critical points in a script. (This is an idea borrowed from C.)

Example 30−3. Testing a condition with an "assert"

#!/bin/bash

# assert.sh

assert

()

# If condition false,

{

 

#+ exit from script with error message.

E_PARAM_ERR=98

 

E_ASSERT_FAILED=99

 

if [

−z "$2" ]

# Not enough parameters passed.

then

 

 

return $E_PARAM_ERR

# No damage done.

Chapter 30. Debugging

284

Advanced Bash−Scripting Guide

fi

lineno=$2

if [ ! $1 ] then

echo "Assertion failed: \"$1\"" echo "File $0, line $lineno" exit $E_ASSERT_FAILED

#else

#return

#and continue executing script.

fi

}

 

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.

exit 0

5.trapping at exit.

The exit command in a script triggers a signal 0, terminating the process, that is, the script itself.

[59]It is often useful to trap the exit, forcing a "printout" of variables, for example. The trap must be the first command in the script.

Trapping signals

trap

Specifies an action on receipt of a signal; also useful for debugging.

A signal is simply 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 ControlC, sends a user interrupt, an INT signal, to a running program.

trap '' 2

# Ignore interrupt 2 (Control−C), with no action specified.

trap 'echo "Control−C disabled."' 2

# Message when Control−C pressed.

Chapter 30. Debugging

285

# Clean up temp file.
# −n option to echo suppresses newline,
# so you get continuous rows of dots.

Advanced Bash−Scripting Guide

Example 30−4. Trapping at exit

#!/bin/bash

trap 'echo Variable Listing −−− a = $a b = $b' EXIT

# EXIT is the name of the signal generated upon exit from a script.

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.

Example 30−5. Cleaning up after Control−C

#!/bin/bash

# logon.sh: A quick 'n dirty script to check whether you are on−line yet.

TRUE=1

LOGFILE=/var/log/messages

#Note that $LOGFILE must be readable (chmod 644 /var/log/messages). TEMPFILE=temp.$$

#Create a "unique" temp file name, using process id of the script. KEYWORD=address

#At logon, the line "remote IP address xxx.xxx.xxx.xxx"

#

 

 

appended to /var/log/messages.

ONLINE=22

 

USER_INTERRUPT=13

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 −1 $LOGFILE> $TEMPFILE

#Saves last line of system log file as temp file. 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 exit $ONLINE

else

echo −n "."

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.

Chapter 30. Debugging

286

Advanced Bash−Scripting Guide

#Exercise: Change the script, as 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 −1 "$LOGFILE" | grep −q "$KEYWORD" do echo −n .

sleep $CHECK_INTERVAL done

echo "On−line"

#Exercise: Consider the strengths and weaknesses

#of each of these approaches.

The DEBUG argument to trap causes a specified action to execute after every command in a script. This permits tracing variables, for example.

Example 30−6. Tracing a variable

#!/bin/bash

trap 'echo "VARIABLE−TRACE> \$variable = \"$variable\""' DEBUG

# Echoes the value of $variable after every command.

variable=29

echo "Just initialized \"\$variable\" to $variable."

let "variable *= 3"

echo "Just multiplied \"\$variable\" by 3."

#The "trap 'commands' DEBUG" construct would be more useful

#in the context of a complex script,

#where placing multiple "echo $variable" statements might be

#clumsy and time−consuming.

#Thanks, Stephane Chazelas for the pointer.

exit 0

Chapter 30. Debugging

287

Advanced Bash−Scripting Guide

trap '' SIGNAL (two adjacent apostrophes) disables SIGNAL for the remainder of the script. trap

SIGNAL restores the functioning of SIGNAL once more. This is useful to protect a critical portion of a script from an undesirable interrupt.

trap '' 2 # Signal 2 is Control−C, now disabled. command

command command

trap 2 # Reenables Control−C

Chapter 30. Debugging

288