I. Preface
Shell programming is widely used in the Unix/linux world, and mastering shell programming is a must for a good unix/linux developer and system administrator. The main task of script debugging is to find out the cause of the script error and locate the line where the error occurred in the script source code, the commonly used methods include parsing the output error message, by adding debug statements in the script, outputting debugging information to assist in diagnosing errors, using debugging tools and so on. However, compared with other high-level languages, Shell interpreter lacks the corresponding debugging mechanism and debugging tool support, the output error message is often very unclear, beginners in debugging scripts, in addition to know with the Echo statement output some information, there is no other method, and only rely on a large number of ECHO statements to diagnose errors, It's really a lot, so it's common for beginners to complain that shell scripts are too difficult to debug. This article will systematically introduce some important shell script debugging techniques, hoping to benefit the shell beginner.
The target audience for this article is the developer, tester and system administrator in the Unix/linux environment, requiring the reader to have basic shell programming knowledge. The examples used in this article are tested under Bash3.1+redhat Enterprise Server 4.0, but the debugging techniques should also apply to other shells. two. Output debugging information in shell scripts
By adding debug statements to the program, it is the most common debugging means to display information about key areas or where errors occur. Shell programmers often use the echo (Ksh programmer often uses print) statements to output information, but relying solely on the output trace of the Echo statement is cumbersome, and the large number of ECHO statements that are added to the script during the debug phase will have to be removed one by one of the time when the product is delivered. To solve this problem, this section mainly introduces some methods of how to easily and effectively output debugging information. 1. Using the trap command
The trap command is used to capture the specified signal and execute a pre-defined command.
Its basic syntax is:
Trap ' command ' signal
Where signal is the signal to be captured, the command is the one to execute after capturing the specified signal. You can use the Kill–l command to see all the available signal names in the system, and the command executed after capturing the signal can be any one or more valid shell statements, or a function name.
When the shell script executes, it produces three so-called "pseudo-signals", which are called "pseudo-signals" because the three signals are generated by the shell and other signals are generated by the operating system, and it is helpful to debug by capturing the three "pseudo-signals" using the trap command and outputting the relevant information. table 1. Shell Pseudo-Signal
Signal name |
when to produce |
EXIT |
Exit from a function or complete script execution |
Err |
When a command returns a non-0 state (on behalf of the command execution is unsuccessful) |
DEBUG |
Before each command in the script executes |
By capturing the exit signal, we can output some of the values of the variables we want to track when the shell script aborts execution or exits from the function, thus judging the execution state of the script and the cause of the error, using the following methods:
Trap ' command ' EXIT or trap ' command ' 0
By capturing the err signal, we can easily trace the execution of unsuccessful commands or functions, and output relevant debugging information, here is a sample program to capture the Err signal, where $lineno is a shell built-in variable that represents the current line number of the shell script.
$ cat-n exp1.sh
1 errtrap ()
2 {
3 echo "[line:$1] Error:command or function exited with status $ ?"
4 }
5 foo ()
6 {
7 return 1;
8 }
9 trap ' errtrap $LINENO ' ERR
ten abc
one foo
The output results are as follows:
$ sh exp1.sh
exp1.sh:line 10:abc:command not found
[line:10] Error:command or function exited with status 127< C2/>[LINE:11] Error:command or function exited with status 1
During debugging, in order to keep track of the values of certain variables, we often need to insert the same echo statement in many parts of the shell script to print the values of the related variables, which is cumbersome and clumsy. By capturing the debug signal, we only need a trap statement to complete the full tracking of the relevant variables.
Here is an example program that tracks variables by capturing the debug signal:
$ cat–n exp2.sh
1 #!/bin/bash
2 trap ' echo ' before execute line: $LINENO, a= $a, b= $b, c= $c "' DEBUG
3< C5/>a=1
4 if ["$a"-eq 1]
5 then
6 b=2
7 Else
8 b=1
9 fi
Ten c=3 echo "End"
The output results are as follows:
$ sh exp2.sh
before execute line:3, a=,b=,c=
before execute line:4, a=1,b=,c=
before execute line:6, a=1,b =,c=
before execute line:10, a=1,b=2,c=
before execute line:11, a=1,b=2,c=3
End
It is clear from the running results that the value of the relevant variable changes after each command execution. At the same time, from the running results of the printed line number to analyze, you can see the entire script execution trajectory, can determine which conditions branch execution, which conditional branches are not executed. 2. Using the tee command
Pipelines and input and output redirects are used very much in shell scripts, and the execution of some commands is directly the input of the next command under the action of the pipeline. If we find that the execution result of a batch of commands connected by a pipeline is not as expected, it is necessary to step through the execution of each command to determine where the problem is, but because the pipeline is used, these intermediate results are not displayed on the screen and are difficult to debug, so we can use the Tee command now.
The tee command reads the data from the standard input, outputs its contents to a standard output device, and saves the content as a file. For example, the following script fragment, which is to get the IP address of the machine:
Ipaddr= '/sbin/ifconfig | grep ' inet addr: ' | Grep-v ' 127.0.0.1 '
| cut-d:-f3 | The whole sentence after the awk ' {print '
#注意 = sign is enclosed in anti-quotation marks (the key to the left of the number 1 key).
Echo $ipaddr
Run this script, the actual output is not the native IP address, but the broadcast address, then we can use the tee command, output some intermediate results, the above script fragment is modified to:
Ipaddr= '/sbin/ifconfig | grep ' inet addr: ' | Grep-v ' 127.0.0.1 '
| tee temp.txt | cut-d:-f3 | awk ' {print $ '
echo $ipaddr
After that, the script is executed again, and then the contents of the Temp.txt file are viewed:
$ cat Temp.txt
inet addr:192.168.0.1 bcast:192.168.0.255 mask:255.255.255.0
We can see that the second column of the intermediate result (separated by the: number) contains the IP address, and the third column is intercepted using the cut command in the script above, so we just need to change the cut-d:-f3 in the script to cut-d:-f2 can get the correct result.
Specific to the script example above, we may not need the help of the tee command, for example, we can segment the various commands connected by the pipeline and view the output of each command to diagnose the error, but in some complex shell scripts, These commands that are connected by pipelines may also depend on some of the other variables defined in the script, which makes it much more cumbersome to run each command at the prompt, and simply inserting a tee command between the pipes to see the intermediate results is more convenient. 3. Use the "Debug hooks"
In the C language program, we often use debug macros to control whether or not to output debug information, and in shell scripts we can also use such a mechanism, as shown in the following code:
If ["$DEBUG" = "true"]; Then
echo "debugging" #此处可以输出调试信息
fi
Such blocks of code are often referred to as "debug hooks" or "Debug blocks". Inside the debug hook can output any of the debugging information you want to output, the advantage of using debug hooks is that it can be controlled by the debug variable, in the development and debugging stage of the script, you can first execute the Export debug=true command to open the debug hook, so that it output debugging information, And when the script is delivered to use, there is no need to bother to delete debug statement one by one in the script.
If you use the IF statement to determine the value of the debug variable in every place where you need to output debugging information, it is still cumbersome to define a debug function to make the process of inserting the debug hooks more concise and convenient, as shown in the following code:
$ cat–n exp3.sh
1 DEBUG ()
2 {
3 if ["$DEBUG" = "true"]; then
4 $@
5 fi
6 }
7 a=1
8 DEBUG echo "a= $a"
9 if ["$a"-eq 1]
ten then one b =2 Else b=1 fi DEBUG echo "b= $b" c=3
17 DEBUG echo "c= $c"
In the debug function shown above, any commands passed to it will be executed, and the execution can be controlled by the value of the debug variable, so it is convenient to call all debug-related commands as parameters of the debug function.
three. Using the shell's execution options
The debugging method described in the previous section is to locate the error by modifying the source code of the shell script so that it outputs relevant debugging information, and there is no way to debug the shell script without modifying the source code. The answer is to use the shell's execution options, and this section describes the usage of some common options:
-N reads only the shell script, but does not actually execute
-X enters the tracking mode, showing each command executed
-C "string" reads commands from strings
"-n" can be used to test for a syntax error in a shell script, but does not actually execute the command. After shell scripting is complete, it is a good practice to first use the "-n" option to test the script for syntax errors before it is actually executed. Because some shell scripts will have an impact on the system environment, such as generating or moving files, or if the syntax errors are found in actual execution, you will have to do some manual recovery of the system environment to continue testing the script.
The "-C" option causes the shell interpreter to read and execute shell commands from a string rather than from a file. You can use this option when you need to temporarily test the execution results of a small piece of script, as follows:
Sh-c ' A=1;b=2;let c= $a + $b; echo "C= $c"
The "-X" option can be used to track script execution and is a powerful tool for debugging shell scripts. The "-X" option causes the shell to display each command line that it actually executes during the execution of the script, and displays a "+" sign at the beginning of the row. The "+" sign is followed by the contents of the command line after the variable substitution, helping to analyze what commands were actually executed. The "-X" option is simple and easy to handle for most shell debugging tasks, and should be used as a preferred debugging tool.
If you combine the trap ' command ' debug mechanism described earlier in this article with the "-X" option, we can output both the actual execution of each command and the value of the relevant variable line by row, which is useful for debugging.
Continue with the exp2.sh described earlier, and now add the "-X" option to execute it:
$ sh–x exp2.sh
+ trap ' echo ' before execute line: $LINENO, a= $a, b= $b, c= $c "' DEBUG
+ + echo ' before execute Line:3, A=,b=,c= '
before execute line:3, a=,b=,c=
+ a=1
+ + echo ' before execute line:4, a=1,b=,c= '
before Execute Line:4, a=1,b=,c=
+ ' [' 1-eq 1 '] '
+ + echo ' before execute line:6, a=1,b=,c= '
before execute line:6, a=1,b=,c=
+ b=2 + +
echo ' before execute line:10, a=1,b=2,c= '
before execute line:10, a=1,b=2,c=
+ c=3
+ + echo ' before execute line:11, a=1,b=2,c=3 '
before execute line:11, a=1,b=2,c=3
+ echo End
End
In the above result, the line preceded by the "+" is the command that the shell script actually executes, and the line preceded by the "+ +" sign executes the command specified in the trap mechanism, while the other rows are output information.
The shell's execution options can be specified in the script, in addition to being specified when the shell is started. Set-parameter means that an option is enabled, and set + parameter means that an option is turned off. Sometimes we don't need to use the "-X" option at startup to keep track of all the command lines, so we can work with the set command in our script, as shown in the following script fragment:
Set-x #启动 the "-X" option
to track the program segment
set +x #关闭 the "-X" option
The set command can also be invoked using the debug hook-debug function described in the previous section, which avoids the hassle of deleting these debug statements when script delivery is used, as shown in the following script fragment:
Debug Set-x #启动 "-X" option
to track the program segment
DEBUG set +x #关闭 "-X" option
Four. Enhancements to the "-X" option
The "-X" execution option is currently the most common means of tracking and debugging shell scripts, but the output of debugging information is limited to the actual execution of each command after the variable substitution and a "+" prompt at the beginning of the line, incredibly even the row number is not important, for the debugging of complex shell scripts , is still very inconvenient. Fortunately, we can use some of the shell's built-in environment variables to enhance the output information of the "-X" option, which introduces several shell-built environment variables:
$LINENO
Represents the current line number of the shell script, similar to the built-in macro in C __line__
$FUNCNAME
The name of the function, similar to the built-in macro __func__ in C, but the macro __func__ can only represent the current function name, and $funcname is more powerful, it is an array variable containing all the functions on the entire call chain name, so the variable ${funcname[0 ]} represents the name of the function that the shell script is currently executing, and the variable ${funcname[1]} represents the name of the function that called the function ${funcname[0]}, and so on.
$PS 4
The main prompt variable $PS1 and the second-level prompt variable $ps2 are more common, but few people notice the effect of the fourth-level prompt variable $PS4. We know that using the "-X" execution option will show each command that was actually executed in the shell script, and that the value of $PS4 will be displayed in front of each command output by the "-X" option. In the bash shell, the default value of $PS4 is the "+" sign. (Now that you know why the "-X" option is used, the output command has a "+" sign in front of it.) )。
Using the $PS4 feature, we can enhance the output information of the "-X" option by redefining the value of the $PS4 using some built-in variables. For example, execute the export ps4= ' +{$LINENO: ${funcname[0]} ' and then execute the script using the '-X ' option to display its line number and the name of the function it belongs to in front of each actually executed command.
The following is an example of a bug-based shell script that this article will use to demonstrate how to debug a shell script with "-N" and enhanced "-X" execution options. A function isroot () is defined in this script to determine if the current user is a root user, and if not, abort the execution of the script
$ cat–n exp4.sh
1 #!/bin/bash
2 isRoot ()
3 {
4 if ["$UID"-ne 0]
5 return 1< C10/>6 Else
7 return 0
8 fi
9 } isRoot if ["$?"-ne 0 ]
then echo "must is root to run this script" exit 1- else
16< C31/>echo "Welcome root user" #do something fi
The Sh–n exp4.sh is executed first for the syntax check, and the output is as follows:
$ sh–n exp4.sh
exp4.sh:line 6:syntax error near unexpected token ' Else '
exp4.sh:line 6: ' Else '
A syntax error was found, and by examining the command before and after the 6th line, we found that the IF statement in line 4th was missing the then keyword (the person who was accustomed to the C program was prone to make the mistake). We can change the 4th line to if ["$UID"-ne 0]; Then to fix this error. Run Sh–n exp4.sh again to check for syntax, and no more errors are reported. The next step is to actually execute the script, and the results are as follows:
$ sh exp4.sh
exp2.sh:line one: [1:command not found
welcome root user
Although the script does not have a syntax error, it reports an error when it executes. The error message is also very strange "[1:command not Found". Now we can try to customize the value of the $PS4 and use the "-X" option to track:
$ export ps4= ' +{$LINENO: ${funcname[0]}} '
$ sh–x exp4.sh +{10
:} isRoot
+{4:isroot} ' [' 503-ne 0 '] '
+{5: IsRoot} return 1
+{11:} ' [1 '-ne 0 '] '
exp4.sh:line one: [1:command not found
+{16:} echo ' Welcome root user '
Welcome Root User
From the output, we can see that the script is actually executed statement, the line number of the statement and the name of the function is also printed out, from which you can clearly analyze the script execution trajectory and the function of the internal execution of the call. Since the execution is the 11th line error, this is an if statement, we compare and analyze the same as if statement of the 4th line of the trace results:
+{4:isroot} ' [' 503-ne 0 '] '
+{11:} ' [1 '-ne 0 '] '
It is known that the 11th line of the [number is missing a space, resulting in the [number and close to its variable $?] The value 1 is considered a whole by the shell interpreter, and try to treat this whole as a command to execute, so there is "[1:command not found" such as the error hint. Just insert a space after the [number] and everything is fine.
There are other built-in variables that are useful for debugging, such as Bash_source in the BASH shell, Bash_subshell, and so on, which are useful for debugging built-in variables that you can view with man sh or man BASH. These built-in variables are then used to customize the $PS4 for your debugging purposes, thus achieving the purpose of enhancing the output information of the "-X" option.