Detailed explanation of script debugging mechanism in bash

  • 2021-07-26 09:17:48
  • OfStack

Run the script in debug mode

Through bash-x < script > To run the entire script in debug mode, bash prints out every 1 line of commands before running, with a + sign in front of each line indicating the number of nested levels of commands.


> bash -x debug.sh 
+ echo 'First line'
First line #  There is no plus sign in the output 
++ date #  Execute command replacement first   Two plus signs because the command is nested within the echo Medium 
+ echo 'Print datetime: Thu 26 Mar 2020 08:21:28 PM CST Done.'
Print datetime: Thu 26 Mar 2020 08:21:28 PM CST Done.

If the script is complex, we can use the environment variable PS4 in conjunction with the built-in variables for debugging to output more detailed information:


> export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
> bash -x debug.sh 
+debug.sh:3:: echo 'First line'
First line
++debug.sh:4:: date
+debug.sh:4:: echo 'Print datetime: Thu 26 Mar 2020 08:35:59 PM CST Done.'
Print datetime: Thu 26 Mar 2020 08:35:59 PM CST Done.

We can also use the DEBUG keyword of trap to execute the specified command or function before interpreting and executing every 1 line of script:


trap 'echo "VARIABLE-TRACE> \$variable = \"$variable\""' DEBUG
variable=29
let variable++
let variable*=5
exit 0

#  The output is as follows 
VARIABLE-TRACE> $variable = ""
VARIABLE-TRACE> $variable = "29"
VARIABLE-TRACE> $variable = "30"
VARIABLE-TRACE> $variable = "150"

You can also use the ERR keyword of trap to perform default actions in the event of an error in interpretation, such as printing an incorrect command:


trap 'echo $BASH_COMMAND' ERR

However, in view of the inefficiency of debugging with trap, it is recommended to use debugging options directly in more complex scripts or use debugging tools such as bashdb.

Advanced debugging

View function call information

Using the built-in command caller in the function can output the call information of the function to stdout, but note that the command must be called inside the function.


#!/usr/bin/bash

func1 () {
  for i in `seq 0 3`
  do
    echo -e "Level$i\t  `caller $i`"
  done
}

func2 () {
  func1
}

func3 () {
  func2
}

func3
caller 0 #  You must call in a function   Otherwise there is no output 
exit 0

Running the script yields the following output:

Direct caller of Level0 11 func2 call. sh # func1
Level1 15 func3 call. sh # Layer 1 Indirect Call
Level2 18 main call. sh # 2 Layer Indirect Call
Level3 # No output because there is no Layer 3 call

Local debugging

Local debugging blocks can be constructed with the set command, and we can add local debugging as follows:


set -x
date
set +x

> bash script1.sh #  You do not need to add debug parameters 
The script starts now.

+ date
Fri 28 Feb 2020 06:23:04 PM CST
+ set +x

This is a string: black
And this is a number: 9

Debug parameter table

短命令 长命令 效果
set -f set -o noglob 对文件名停用元字符匹配
set -v set -o verbose 打印输入的命令
set -x set -o xtrace 命令行首打印+,执行出错会打印详细信息

Parameters for debugging can be dynamically superimposed or deleted at run time:


> set -v
> date
date 
Fri 28 Feb 2020 06:54:47 PM CST
> set -x      #  Parameters can be accumulated 
date        # -v  The effect of 
+ date       # -x  The effect of 
Fri 28 Feb 2020 06:55:37 PM CST
> set +vx      #  Cancel parameter 
set +vx

You can significantly reduce escape characters in scripts by using the-f option:


> ls ?
x86_64-pc-linux-gnu-library
> set -f      #  Disable metacharacter matching 
> ls ?
ls: cannot access '?': No such file or directory
> touch ?
> ls ?
'?'
> rm ?
> set +f -x   #  Options  x  It can also be used to display detailed error messages 
> aaa
+ aaa
+ '[' -x /usr/lib/command-not-found ']'
+ /usr/lib/command-not-found -- aaa

Command 'aaa' not found, did you mean:

 command 'aha' from deb aha (0.5-1)
 command 'jaaa' from deb jaaa (0.8.4-4)
 command 'aa' from deb astronomical-almanac (5.6-6)

Try: sudo apt install <deb name>

+ return 127

Default debugging

You can also add parameters directly to line 1 of the script so that the script starts in debug mode by default:


#!/bin/bash -xv

You can also use echo to output debugging information before commands that may go wrong:


> export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
> bash -x debug.sh 
+debug.sh:3:: echo 'First line'
First line
++debug.sh:4:: date
+debug.sh:4:: echo 'Print datetime: Thu 26 Mar 2020 08:35:59 PM CST Done.'
Print datetime: Thu 26 Mar 2020 08:35:59 PM CST Done.
0

Setting Options to Assist Debugging

To facilitate debugging, we can use the set command to set the options of bash:


> export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
> bash -x debug.sh 
+debug.sh:3:: echo 'First line'
First line
++debug.sh:4:: date
+debug.sh:4:: echo 'Print datetime: Thu 26 Mar 2020 08:35:59 PM CST Done.'
Print datetime: Thu 26 Mar 2020 08:35:59 PM CST Done.
1

Common debugging options

References to define variables report errors:


> export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
> bash -x debug.sh 
+debug.sh:3:: echo 'First line'
First line
++debug.sh:4:: date
+debug.sh:4:: echo 'Print datetime: Thu 26 Mar 2020 08:35:59 PM CST Done.'
Print datetime: Thu 26 Mar 2020 08:35:59 PM CST Done.
2

To prevent misoperation from overwriting the data in the file, set the prohibition of redirecting to an existing file:


> set -C #  Equivalent to  set -o noclobber
> touch test
> date > test
bash: test: cannot overwrite existing file

Set the wildcard character not to be resolved:


> export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
> bash -x debug.sh 
+debug.sh:3:: echo 'First line'
First line
++debug.sh:4:: date
+debug.sh:4:: echo 'Print datetime: Thu 26 Mar 2020 08:35:59 PM CST Done.'
Print datetime: Thu 26 Mar 2020 08:35:59 PM CST Done.
4

Related articles: