Overview
In this tutorial, we’ll look at the various techniques to debug Bash shell scripts. The Bash shell doesn’t provide any built-in debugger. However, there are certain commands and constructs that can be utilized { 利用 } for this purpose.
First, we’ll discuss the usages of the set command for debugging scripts. After that, we’ll check a few debugging specific use-cases using the set and trap commands. Finally, we’ll present some methods to debug already running scripts.
Bash Debugging Options
The debugging options available in the Bash shell can be switched on and off in multiple ways. Within scripts, we can either use the set command or add an option to the shebang line. However, another approach is to explicitly specify the debugging options in the command-line while executing the script. Let’s dive into the discussion.
Enabling verbose Mode
We can enable the verbose mode using the -v switch, which allows us to view each command before it’s executed.
To demonstrate this, let’s create a sample script:
|
|
This script checks whether or not the number entered as input is positive.
Next, let’s execute our script:
|
|
As we can notice, it prints every line of the script on the terminal before it’s processed.
We can also add the -v option in the shebang line:
|
|
This has the same effect as explicitly calling a script using bash -v. Another equivalent is to enable the mode within a script using set command:
|
|
In fact, we can use either of the ways discussed above to enable the various switches that we’ll discuss henceforth { from this time into the future }.
Syntax Checking Using noexec Mode
There can be situations where we may want to validate the script syntactically prior to its execution. If so, we can use the noexec mode using the -n option. As a result, Bash will read the commands but not execute them.
Let’s execute our positive_check.sh script in noexec mode:
|
|
This produces a blank output since there are no syntax errors. Now, we’ll modify our script a bit and remove the then statement:
|
|
Next, we’ll validate it syntactically with -n option:
|
|
As expected, it threw an error since we missed the then statement in the if condition .
Debugging Using xtrace Mode
In the previous section, we tested the script for syntax errors. But for identifying logical errors, we may want to trace the state of variables and commands during the execution process. In such instances, we can execute the script in xtrace (execution trace) mode using the -x option.
This mode prints the trace of commands for each line after they are expanded but before they are executed.
Let’s execute our positive_check.sh script in execution trace mode:
|
|
Here we can see the expanded version of variables on stdout before execution. It’s important to note that the lines preceded by + sign are generated by the xtrace mode.
Identifying Unset Variables
Let’s run an experiment to understand the default behavior of unset variables in Bash scripts:
|
|
We’ll now execute the above script:
|
|
As we can notice, there’s an issue: The script executed successfully, but the output is logically incorrect.
We’ll now execute the script with the -u option:
|
|
Certainly, there’s a lot more clarity now!
The -u option treats unset variables and parameters as an error when performing parameter expansion. Consequently, we get an error notification that a variable is not bound to value while executing the script with -u option
Use Cases to Debug Shell Scripts
So far, we saw the various switches for debugging scripts. Henceforth, we’ll look at some use-cases and methods to implement these in shell scripts.
Combining Debugging Options
To get better insights, we can further combine the various options of the set command.
Let’s execute our add_values.sh script with both -v and -u options enabled:
|
|
Here, by enabling the verbose mode with the -u option, we could easily identify the statement triggering the error.
Similarly, we can combine the verbose and xtrace mode to get more precise { 精确的 } debug information.
As discussed previously, the -v option shows each line before it is evaluated, and the -x option shows each line after they are expanded. Hence, we can combine both -x and -v options to see how statements look like before and after variable substitutions.
Now, let’s execute our positive_check.sh script with -x and -v mode enabled:
|
|
We can observe that the statements are printed on stdout before and after variable expansion.
Debugging Specific Parts of the Script
Debugging with -x or -v option shell scripts generates an output for every statement on stdout. However, there can be situations where we may want to reduce debug information to only specific parts of the script. We can achieve that by enabling the debug mode before the code block starts, and later reset it using the set command.
Let’s check it with an example:
|
|
Here, we could debug only the if condition using the set statement before the condition starts. Later, we could reset the xtrace mode after the if block ends using the set +x command.
Let’s validate it with the output:
|
|
Certainly, the output looks less cluttered.
Redirecting Only the Debug Output to a File
In the previous section, we examined how we can restrict debugging to only certain parts of the script. Consequently { 因此 }, we could restrict the amount of output on stdout.
Furthermore, we can redirect the debug information to another file and let the script output print on stdout.
Let’s create another script to check it:
|
|
First, we opened the debug.log file on File Descriptor (FD)5 for writing using the exec command.
Then we changed the special shell variable PS4. The PS4 variable defines the prompt that gets displayed when we execute a shell script in xtrace mode. The default value of PS4 is +. We changed the value of the PS4 variable to display line numbers in the debug prompt. To achieve this, we used another special shell variable LINENO.
Later, we assigned the FD 5 to Bash variable BASH_XTRACEFD. In effect, Bash will now write the xtrace output on FD 5 i.e. debug.log file. Let’s execute the script:
|
|
As expected, the debug output is not written on the terminal. Although, the first few lines, until FD 5 is assigned to debug output were printed.
Additionally, the script also creates an output file debug.log, which contains the debug information:
|
|
Debugging Scripts Using trap
**We can utilize the DEBUG trap feature of Bash to execute a command repetitively. The command specified in the arguments of trap command is executed before each subsequent statement in the script.
Let’s illustrate this with an example:
|
|
In this example, we specified the echo command to print the values of variables five_val, two_val, and total. Subsequently, we passed this echo statement to the trap command with the DEBUG signal. In effect, prior to the execution of every command in the script, the values of variables get printed.
Let’s check the generated output:
|
|
Debugging Already Running Scripts
So far, we presented methods to debug shell scripts while executing them. Now, we’ll look at ways to debug an already running script.
Consider a sample running script which executes sleep in an infinite while loop :
|
|
With the help of pstree command, we can check the child processes forked by our script sleep.sh:
|
|
We used an additional option -p to print the process ids along with the process names. Hence, we’re able to realize { 意识到;领悟;理解 } that the script is waiting for the child processes (sleep) to complete.
Sometimes we may want to have a closer look at the operations performed by our processes. In such cases, we can use the strace command to trace the Linux system calls in progress:
|
|
Here we used the option -p to attach to the process id (372) i.e. our script in execution. Additionally, we also used the -f option to attach to all its child processes. Note that, the strace command generates output for every system call. Hence, we used the -c option to print a summary of the system calls at the termination of strace.
Conclusion
In this tutorial, we studied multiple techniques to debug a shell script.
In the beginning, we discussed the various options of set command and their usage for debugging scripts. After that, we implemented several case-studies to study a combination of debugging options. Alongside this { 除此之外 }, we also explored ways to restrict debug output and redirect it to another file.
Next, we presented a use-case of the trap command and DEBUG signal for debugging scenarios. Finally, we offered a few approaches to debug already running scripts.