Shell is a program for the interaction between users and the operating system. It is often used to perform some automated or repetitive and cumbersome tasks. Now all Linux systems basically bring this program. We only need to write shell scripts and execute them directly. There is no need to install additional software and configure the compilation environment. It can be said that it is very convenient to use, However, it is often impressive in debugging. This paper mainly introduces the common debugging methods of shell scripts
Common debugging options
When debugging a shell script, several debugging options are often used. During the execution of the script, some debugging information will be output. According to the debugging information, the specific problem code can be located
The specific options and descriptions are as follows:
option | explain |
---|---|
-x | Output the executed command before outputting the result |
-u | If a variable does not exist, an error will be reported and execution will be stopped |
-e | When an error occurs, the execution is terminated |
-n | Check for syntax errors |
-o pipefail | An error occurred in the pipeline subcommand, terminating execution |
Track script execution
- Output debugging information
Usually, after the script is executed, only the results are output. When multiple commands are run, multiple results will be output continuously. It is impossible to distinguish which command corresponds to which result. Using the - x option, the debugging information of the command to be executed will be output first, and then the command will be executed
The function of the existing script ta.sh is to output the current date, as follows
#!/bin/bash echo "today is :"$(date +'%Y-%m-%d')
We use the - x option to execute the script, and the results are as follows
[root@VM-0-2-centos shell_debug]# bash -x ta.sh ++ date +%Y-%m-%d + echo 'today is :2021-07-10' today is :2021-07-10
It can be seen from the results that each line of command is printed out before execution. The + sign in front of the line indicates debugging information. It is actually the value of environment variable PS4. The first character of PS4 will be repeated according to the nesting level. The deeper the command level is, the more + signs in front
In the result, the first line indicates that the date +'%Y-%m-%d' command is executed, which is in the inner layer, so two + signs are printed. The second line indicates that the echo "today is:" $(date +'%Y-%m-%d') command is executed, which is in the outer layer, and only one + sign is printed
Put the - x option in #/ After the bin/bash statement, the same effect can be achieved without - X. The above script only needs to put #/ Change bin/bash to #/ bin/bash -x is enough
- Output line number
The script content in the above example is very few. Imagine that if the script content reaches hundreds or thousands of lines, it will be very difficult to read the prompt information of each line of command. In this case, we can directly locate the specific line by adding the line number before the output of each line
Modify the ta.sh script. The modified content is as follows
#!/bin/bash PS4='+${BASH_SOURCE}:${LINENO} ' echo "start..." set -x echo "today is :"$(date +'%Y-%m-%d') set +x echo "end..."
The modified script adds PS4 variable, which is the prefix of debugging information. The default value is "+". We can modify its value to achieve the purpose of including line number in the output debugging information
In the above code, "${BASH_SOURCE}" represents the relative path of the currently executed shell script, which is used here to represent the script file name, "${LINENO}" represents the line number. After modifying PS4, the output debugging information will include the script name and line number
Let's execute the script and see the results
[root@VM-0-2-centos shell_debug]# bash -x ta.sh + PS4='+${BASH_SOURCE}:${LINENO} ' +ta.sh:4 echo start... start... ++ta.sh:5 date +%Y-%m-%d +ta.sh:5 echo 'today is :2021-07-10' today is :2021-07-10 +ta.sh:6 echo end... end...
It can be seen from the results that the debugging information of each command line contains the file name and line number
- Output some debugging information
Sometimes, we only need to output some debugging information. At this time, we need to manually set the - x option and put the command that needs to output debugging information between set -x and set +x
Modify the ta.sh script as follows
#!/bin/bash echo "test..." set -x echo "today is :"$(date +'%Y-%m-%d') set +x echo "finish..."
Execute the script and the results are as follows
[root@VM-0-2-centos shell_debug]# ./ta.sh [root@VM-0-2-centos shell_debug]# ./ta.sh test... ++ date +%Y-%m-%d + echo 'today is :2021-07-10' today is :2021-07-10 + set +x finish...
It can be seen from the results that only echo today is: "$(date + '% Y -% m -% d') command outputs debugging information. set -x is equivalent to turning on debugging information, and set +x is to turning off debugging information
Note that when set -x is used in the script, do not add - x during execution
Log printing
It is a common way to debug shell scripts by printing logs. Print variable values or command results before and after a line of commands, and judge whether there are errors through logs
However, when the script is relatively long, there are a little more logs to print. Moreover, after debugging, these debugging logs are no longer needed. At this time, delete the logs and print them line by line
The following describes a method to add a switch to all log printing in the script. When the switch is turned on, the debugging related logs will be output. When it is not needed, turn off the switch directly
The existing script debug1.sh is as follows
#!/bin/bash #Commissioning switch, on means on, others means off IS_DEBUG="on" #Debug switch function function _DEBUG() { [ "$IS_DEBUG" == "on" ] && $@ } va=1 _DEBUG echo 'old value:'$va #Variable val plus 1 let va++ echo 'new value:'$va
In the above script, the IS_DEBUG variable is the debugging switch, "on" means on, and others means off
_DEBUG() is a debugging switch function. Its function is: if IS_DEBUG is "on", execute the following commands, otherwise ignore them
First turn on the debugging switch and execute the script. The results are as follows
[root@VM-0-2-centos shell_debug]# ./debug1.sh old value:1 new value:2
Then turn off the debugging switch and execute the script. The results are as follows
[root@VM-0-2-centos shell_debug]# ./debug1.sh new value:2
It can be seen from the above two groups of test results that when the debugging switch is turned on, that is, after IS_DEBUG = "on" is set, the statement _debugecho 'old value:' '$va will execute the echo' old value: '' $va command. When IS_DEBUG = "off", the echo 'old value:' '$va command will be ignored
Therefore, when debugging, turn on the debugging switch. After debugging, the script does not need to be modified. Just turn off the switch, and the debugging related commands will not be executed
Common error handling
- Non existent variable
When executing a script, if a non-existent variable is encountered, it will be ignored by default
The existing script td.sh is as follows
#!/bin/bash echo "start..." echo $ta echo "end..."
ta in the script is a non-existent variable. The execution results of the script are as follows
[root@VM-0-2-centos shell_debug]# ./td.sh start... end...
As you can see, echo $ta outputs a blank line. The script directly ignores the nonexistent ta variable and continues to execute the following commands
This is usually not the result we want. If we encounter non-existent variables, we should directly report an error and stop executing the following commands. Add a set -u statement at the beginning of the script or - u when executing the script to get the desired result
Add a set -u statement at the beginning of the script. The whole script is as follows
#!/bin/bash set -u echo "start..." echo $ta echo "end..."
Execute the script and the results are as follows
[root@VM-0-2-centos shell_debug]# ./td.sh start... ./td.sh: line 5: ta: unbound variable
You can see that after adding the set -u statement, if you encounter a non-existent variable ta, you will directly report an error and stop executing the following commands
Of course, we will get the same result when we execute the script with bash -u td.sh command
- syntax error
Syntax error is one of the causes of shell script execution error. When executing the script, add - n. when the script has syntax error, it will not continue to execute, but print the error message
The existing script te.sh is as follows
#!/bin/bash if [ $# -le 0 ];then echo "no param.."
Enter bash -n te.sh command and enter, the result is as follows
[root@VM-0-2-centos shell_debug]# bash -n te.sh te.sh: line 5: syntax error: unexpected end of file
The if in the above script lacks the ending fi, so a syntax error prompt will appear after executing the bash -n te.sh command
This option is very practical, especially after we write the shell script, don't rush to execute it. First use the - n option to check whether there are syntax errors. It can help us find errors in advance
- An error occurred and the execution was terminated
Generally, if an error occurs during script execution, the following commands will continue to be executed
The existing script tf.sh is as follows
#!/bin/bash echo "start..." abc echo "end..."
Execute the script and the results are as follows
[root@VM-0-2-centos shell_debug]# ./tf.sh start... ./tf.sh: line 4: abc: command not found end...
It can be seen from the results that abc in the fourth line of the script is an unknown command, and an error occurred during execution, but the script continues to execute backward until the end
This behavior is not conducive to script security and error troubleshooting. In practical applications, if an error occurs, you should stop executing the script to prevent the accumulation of errors. We can use the - e option to avoid this problem
Add the - e option and execute the above script again. The results are as follows
[root@VM-0-2-centos shell_debug]# bash -e ./tf.sh start... ./tf.sh: line 4: abc: command not found
From the above results, we can see that an error occurred when the script was executed to the fourth line, and the script stopped executing
- Pipeline subcommand failed, terminating execution
The - e option mentioned above has a special case, which is not applicable to the pipeline command. The pipeline command is a command combined with the pipeline symbol "|". See the following example for details
The existing script tg.sh is as follows
#!/bin/bash echo "start..." abc | echo "111" echo "end..."
In the fourth line of the script, abc | echo "111" is the pipeline command. After we execute bash - E. / tg.sh, the results are as follows
[root@VM-0-2-centos shell_debug]# bash -e ./tg.sh start... ./tg.sh: line 4: abc: command not found 111 end...
You can see that even if you use the - e option to execute the script, when an error occurs, it will continue to execute until the end
We use set -o pipefail to solve this situation. As long as a sub command in the pipeline command has an error, the whole pipeline command fails and the script will terminate execution
Modify the above script as follows
#!/bin/bash set -o pipefail echo "start..." abc | echo "111" echo "end..."
Execute the script again and the result is as follows
[root@VM-0-2-centos shell_debug]# bash -e tg.sh start... tg.sh: line 5: abc: command not found 111
It can be seen that after the set -o pipefail statement is added at the beginning of the tg.sh script, the script is executed again. An error occurs when the pipeline command abc | echo "111" executes the sub command abc, the subsequent sub commands are no longer executed, and the whole pipeline command fails
Because the - e option is added during execution, when the pipeline command fails, the script will terminate execution, so echo "end..." is not executed