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
Tip
Use Bash Attributes to TroubleshootSuppose 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
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
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
That bug was easily spotted. After a quick correction, the script works. $ ./debug-me Calculate the total cost The total is 120 Trace Script ExecutionOur 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
Display Executed StatementsThe 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
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 ErrorTo 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. |