脚本错误定位

警告
本文最后更新于 2023-10-22,文中内容可能已过时。

脚本错误定位

常见错误

编写 Shell 脚本的时候,一定要考虑到命令失败的情况,否则很容易出错。

1
2
3
4
5
6
#! /bin/bash

dir_name=/path/not/exist

cd $dir_name
rm *

上面脚本中,如果目录$dir_name不存在,cd $dir_name命令就会执行失败。这时,就不会改变当前目录,脚本会继续执行下去,导致rm *命令删光当前目录的文件。

如果改成下面的样子,也会有问题。

1
cd $dir_name && rm *

上面脚本中,只有cd $dir_name执行成功,才会执行rm *。但是,如果变量$dir_name为空,cd就会进入用户主目录,从而删光用户主目录的文件。

下面的写法才是正确的。

1
[[ -d $dir_name ]] && cd $dir_name && rm *

上面代码中,先判断目录$dir_name是否存在,然后才执行其他操作。

如果不放心删除什么文件,可以先打印出来看一下。

1
[[ -d $dir_name ]] && cd $dir_name && echo rm *

上面命令中,echo rm *不会删除文件,只会打印出来要删除的文件。

bash-x参数

bash-x参数可以在执行每一行命令之前,打印该命令。一旦出错,这样就比较容易追查。

下面是一个脚本script.sh

1
2
# script.sh
echo hello world

加上-x参数,执行每条命令之前,都会显示该命令。

1
2
3
$ bash -x script.sh
+ echo hello world
hello world

上面例子中,行首为+的行,显示该行是所要执行的命令,下一行才是该命令的执行结果。

其实这个就是set -x的效果,这命令我们在《set 命令和 shopt 命令》的set -x小节学习过

环境变量

有一些环境变量常用于错位 u 定位,我们可以用这些变量来手动记录错误日志

  • LINENO:变量LINENO返回使用这个变量的命令在脚本里面的行号。

  • FUNCNAME:变量FUNCNAME返回一个数组,内容是当前的函数调用堆栈。该数组的 0 号成员是当前调用的函数,1 号成员是调用当前函数的函数,以此类推。最后一位是最开始的调用发起者,一般都是函数或者脚本名。

  • BASH_SOURCE:变量BASH_SOURCE返回一个数组,内容是当前的脚本调用堆栈。该数组的 0 号成员是当前执行的脚本,1 号成员是调用当前脚本的脚本,以此类推,跟变量FUNCNAME是一一对应关系。最后一位是最开始的调用发起者,一般都是函数或者脚本名。

  • BASH_LINENO:变量BASH_LINENO返回一个数组,内容是每一轮调用对应的行号${BASH_LINENO[$i]}${FUNCNAME[$i]}是一一对应关系,表示${FUNCNAME[$i]}在调用它的脚本文件${BASH_SOURCE[$i+1]}里面的行号。最后一个元素,即,第一个调用所在的行号,一般都是 0。

简单实践如下:

创建脚本shell2.sh

1
2
3
4
5
6
7
8
9
#!/usr/bin/env bash

function shell_fun2(){
    echo =========================== 
    echo "${LINENO}" 
    echo "${BASH_SOURCE[@]}" 
    echo "${BASH_LINENO[@]}" 
    echo "${FUNCNAME[@]}" 
}

shell1.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/usr/bin/env bash

function shell_fun1(){
    echo =========================== 
    echo "${LINENO}" 
    echo "${BASH_SOURCE[@]}" 
    echo "${BASH_LINENO[@]}" 
    echo "${FUNCNAME[@]}" 
    shell_fun2
}

main.sh

1
2
3
4
5
6
7
#!/usr/bin/env bash

# 引用其他脚本
. ./shell1.sh
. ./shell2.sh

shell_fun1

赋权后执行main.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ ./main.sh 
===========================
5
./shell1.sh ./main.sh
6 0
shell_fun1 main
===========================
5
./shell2.sh ./shell1.sh ./main.sh
9 6 0
shell_fun2 shell_fun1 main

直接看shell_fun2的输出

BASH_SOURCE:main.sh调用shell1.shshell1.sh调用shell2.sh

BASH_LINENO:在交互式 shell 中调用main.sh为第 0 行(约定),main.sh调用shell1.sh,是在第 6 行,注释不算行,shell1.sh调用shell2.sh,是在第 9 行。

FUNCNAME:main 脚本调用 shell_fun1,shell_fun1 调用 shell_fun2。

0%