Learn Bash debugging in 10 minutes

Keywords: Linux shell bash

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:

optionexplain
-xOutput the executed command before outputting the result
-uIf a variable does not exist, an error will be reported and execution will be stopped
-eWhen an error occurs, the execution is terminated
-nCheck for syntax errors
-o pipefailAn 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

Posted by bluwe on Tue, 02 Nov 2021 08:41:08 -0700