Project82.Debug Your Scripts


Project 82. Debug Your Scripts

"My script doesn't work, and I can't figure out where it's going wrong. How do I debug it?"

This project looks at some useful attributes provided by the Bash shell to help in debugging a script. Projects 9 and 10 introduce the basics of shell scripting. Project 45 covers Bash shell attributes.

Learn More

Project 45 covers Bash shell attributes.


Tip

If you write example scripts to try the debugging techniques, be sure to make the script executable (see Project 9).


Use Bash Attributes to Troubleshoot

Suppose that you've just written a 100-line script. You run it, and it goes horribly wrong. It's time to start debugging. Bash provides several shell attributes to aid debugging. These attributes are normally switched off and are activated by the built-in set command. Multiple switches can be placed in a script so that attributes can be turned on and off selectively within specified sections.

To switch on an attribute nounset, for exampletype

$ set -o nounset


To switch off an attribute, type

$ set +o nounset


We'll write a simple shell script, complete with a couple of errors, to demonstrate some debugging techniques. Here's our script.

$ cat debug-me #!/bin/bash # debugging set -o noexec echo "Calculate the total cost" price=12; quantity=10 total=((price*quantity)) echo "The total is $totl"


You'll notice on line 4 the statement set -o noexec, which sets the noexec attribute. This attribute instructs Bash to parse the script and check it for syntax errors, but without actually executing it. Setting noexec is probably the first step in testing a new script. We are able to eliminate syntax errors quickly, without ever executing the script (and potentially doing some harm if the script goes wrong).

Now let's test-run the script and check it for syntax errors.

$ ./debug-me ./debug-me: line 7: syntax error near unexpected token `(' ./debug-me: line 7: `total=((price*quantity))'


Note

Don't set an attribute on the command line and expect it to carry through to a shell script. A script is executed by a new instance of the shell that does not inherit attributes set in the parent shell.


One syntax error is reported; we'll correct it by changing line 7

total=$((price*quantity))


(This is the correct syntax for integer arithmetic evaluation.)

We'll run the script again, having removed the line that sets noexec.

$ ./debug-me Calculate the total cost The total is


Tip

Set the noexec attribute as an easy way to comment out the tail end of a script as you progressively debug it. Place the command in the script and relocate it as you progressively execute more statements.


Another bug has surfaced. Our variable total, which is supposed to hold the calculated total, seems not to do so. A rich area for bug catching is that of misspelled variable names. One way we can catch such errors is to request that Bash disallow the reading of unset variables. Near the top of the file, add the line

set -o nounset


(This reads no unset, not noun set.) Run the script again.

$ ./debug-me Calculate the total cost ./debug-me: line 8: totl: unbound variable


Tip

Set the nounset attribute in your scripts as a matter of course. Occasionally, you'll want to do what it disallows; in these occasions, simply remove it or, better still, switch it off and back on again around the statements you want to exempt. To switch it off, specify +o instead of -o to the set command.


That bug was easily spotted. After a quick correction, the script works.

$ ./debug-me Calculate the total cost The total is 120


Trace Script Execution

Our second example script contains some very simple branching. It's supposed to check whether an argument has been passed to the script and then print whichever of two messages is appropriate: An argument is required if none was passed or Ok if an argument was passed. Let's view and then run the script without giving it an argument.

$ cat debug-me2 #!/bin/bash if [ "$1" = "" ]; then  echo "Usage: An argument is required" fi echo "Ok" $ ./debug-me2 Usage: An argument is required Ok


Whoops! It printed both messages. Quantum mechanics aside, it can't have and not have an argument at the same time. Let's trace through the script by setting the verbose attribute. Every statement that's read will be echoed to the Terminal screen. Near the top of the file, add the line

set -o verbose


Now run the script.

$ ./debug-me2 if [ "$1" = "" ]; then  echo "Usage: An argument is required" fi Usage: An argument is required echo "Ok" Ok


Following this through, we see each statement echoed as it's read. Interspaced with this debugging output is the actual script output Usage: An argument is required and Ok.

The problem (which is obvious in such a short script) is that we've missed the exit statement from just before the end of the if statement. We'll add the missing exit statement and (if it's not too presumptuous) switch off verbose and try again.

$ ./debug-me2 Usage: An argument is required


Now the script works.

Tip

Set both the xtrace and the verbose options to make it easier to follow a long script through execution. You'll see all statements echoed as they are read, plus those that are executed, expanded, and marked by a plus sign.


Display Executed Statements

The shell attribute xtrace provides an alternative tracing facility. Like verbose, it causes statements to be displayed, but unlike verbose, it displays only those statements that are executed. Remember verbose causes statements to be displayed as they are read, whether they are executed or not. Additionally, xTRace echoes statements after the shell has expanded them, so you see the statements as they will be executed; that can be very useful when debugging. Let's try it out. Near the top of the file, add the line

set -o xtrace


Tip

Set the xTRace option on the command line to aid the debugging of interactive commands. This technique can be especially useful when debugging shell or alias expansion, because each line is echoed after all the expansion has taken place, and you see exactly what the shell executes.


We'll run the script twice, first without and then with an argument. Trace statements are shown preceded by a plus symbol.

$ ./debug-me2 + '[' '' = '' ']' + echo 'Usage: An argument is required' Usage: An argument is required + exit $ ./debug-me2 hello + '[' hello = '' ']' + echo Ok Ok


In the trace output, you'll see the if statement after expansion and the two alternative echo statements.

Exit on Error

To terminate a script when it executes a command that fails, set the exit-on-error attribute, errexit,

set -o errexit


Whenever a command (mkdir or cp, for example) is executed and fails, the script terminates. This attribute relies on a command's return code. As discussed in Project 81, all commands return a number when they exit; a return code of 0 means success; nonzero return codes indicate errors; and different commands return different numbers depending on the type of error. Check a command's man page to find out what codes it's likely to return. This technique can be used to put the brakes on a script during debugging, ensuring that it doesn't continue after a failed command, executing potentially harmful statements.




Mac OS X UNIX 101 Byte-Sized Projects
Mac OS X Unix 101 Byte-Sized Projects
ISBN: 0321374118
EAN: 2147483647
Year: 2003
Pages: 153
Authors: Adrian Mayo

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net