脚本错误定位
脚本错误定位
常见错误
编写 Shell 脚本的时候,一定要考虑到命令失败的情况,否则很容易出错。
#! /bin/bash
dir_name=/path/not/exist
cd $dir_name
rm *
上面脚本中,如果目录 $dir_name 不存在,cd $dir_name 命令就会执行失败。这时,就不会改变当前目录,脚本会继续执行下去,导致 rm * 命令删光当前目录的文件。
如果改成下面的样子,也会有问题。
cd $dir_name && rm *
上面脚本中,只有 cd $dir_name 执行成功,才会执行 rm *。但是,如果变量 $dir_name 为空,cd 就会进入用户主目录,从而删光用户主目录的文件。
下面的写法才是正确的。
[[ -d $dir_name ]] && cd $dir_name && rm *
上面代码中,先判断目录 $dir_name 是否存在,然后才执行其他操作。
如果不放心删除什么文件,可以先打印出来看一下。
[[ -d $dir_name ]] && cd $dir_name && echo rm *
上面命令中,echo rm * 不会删除文件,只会打印出来要删除的文件。
bash 的 -x 参数
bash 的 -x 参数可以在执行每一行命令之前,打印该命令。一旦出错,这样就比较容易追查。
下面是一个脚本 script.sh。
# script.sh
echo hello world
加上 -x 参数,执行每条命令之前,都会显示该命令。
$ 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
#!/usr/bin/env bash
function shell_fun2(){
echo ===========================
echo "${LINENO}"
echo "${BASH_SOURCE[@]}"
echo "${BASH_LINENO[@]}"
echo "${FUNCNAME[@]}"
}
shell1.sh
#!/usr/bin/env bash
function shell_fun1(){
echo ===========================
echo "${LINENO}"
echo "${BASH_SOURCE[@]}"
echo "${BASH_LINENO[@]}"
echo "${FUNCNAME[@]}"
shell_fun2
}
main.sh
#!/usr/bin/env bash
# 引用其他脚本
. ./shell1.sh
. ./shell2.sh
shell_fun1
赋权后执行 main.sh
$ ./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.sh,shell1.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。