shell 在 linux 系统中比较常见,简单的脚本可以看着确实没难度,但是当脚本功能复杂后,看起来就不那么流畅了,所以掌握一些调试方式还是很有必要的,这里我收集了一次常用的调试方式。
shell调试的方法
- echo 语句
通过在脚本代码中插入 echo 语句输出变量值、执行状态等信息,在脚本中直接打印还是比较方便的 - shell 脚本本身提供的一些方法
- -n 读一遍脚本中的命令但不执行,主要用于检查脚本中的语法错误。
- -v 一边执行脚本,一边将执行过程中的脚本命令打印到标准输出。
- -x 提供跟踪执行信息,将执行的每一条命令和结果依次打印出来。
- +x 关闭跟踪执行信息。 echo $? 返回值 0 : 命令成功执行 #检测语法,但不实际执行 sh -nv 脚本文件 #边执行,边显示结果,遇到错误就停止脚本 sh -xv 脚本文件
- trap 命令
使用 trap 命令可以捕获并处理脚本中的信号或错误。通过在脚本中设置 trap 命令,可以在发生特定事件时执行自定义操作,如输出日志、打印变量值等。 - 调试工具
使用 trap 命令可以捕获并处理脚本中的信号或错误。通过在脚本中设置 trap 命令,可以在发生特定事件时执行自定义操作,如输出日志、打印变量值等。
在脚本中用set命令。
set -xv表示启用;
set +xv表示禁用。
当Shell脚本没有执行出想要的结果或者打印出了一个错误的时候,可以调试一下,看看执行的详细过程,shell脚本不会像python那样可以单步跟踪,而是使用bash -x [需要执行的脚本],一次性把脚本的执行信息全部打出来,如下图:
linux-86:/opt/zsmTest # bash -x ./test.sh
++ ps -ef
++ grep mesos-master
++ grep -v grep
++ awk '{print $2}'
+ mesos_master_pid=16036
+ echo 16036
16036
linux-86:/opt/zsmTest #
每一行前面都会有“+”,“+”越多表示该句代码的嵌套层次越深。在脚本执行中,如果有语法错误或者脚本中写的路径找不到等问题,会直接有错误信息打屏的。
这篇文章包含开发shell脚本之前首先要了解的东西、刚接触shell开发容易遇到的常见错误等。其中有些总结是我师傅总结的,我在上增加了些自己的经验和教训,然后再把这些东西发出来给大家共享一下
1. 什么是环境变量
环境变量可以理解为进程内一种特殊的全局变量。在进程的内存中有一个全局变量,这是一个字符串数组,每个字符串里面的内容就是"key=value"形式的环境变量。当使用export增加或者修改一个环境变量时,实际就是修改的__environ数组。当我们输入一个命令时,比如ls,bash解释器就从这个数组中查找PATH环境变量的值,这个值是冒号分隔的目录列表,再按这个目录列表从前往后依次查找ls可执行程序。可以使用gdb调试进程,看到具体的__environ数组的值(详见附件)
2. 由iMAP执行 . svc_profile.sh看脚本执行方式
在进入iMAP的主目录后首先要执行. svc_profile.sh,而我们平时执行脚本的时候都是通过“./test.sh”或者“bash test.sh”执行脚本这种方式,那么,这两种方式有何不同呢:
1) 通过“点+空格+脚本”的执行脚本,是当前bash解释器进程读取并执行该脚本,所以svc_profile.sh里面设置的环境变量就会在当前bash进程中生效。在后台登陆sybase数据库时,就需要使用这种方式执行sybase的环境变量,否则会报一个类似于这样的错误:
dbuser@linux:/opt/sybase/ASE-15_0/install> /opt/sybase/ASE-15_0/bin/dataserver: error while loading shared libraries: libsbgse2.so: cannot open shared object file: No such file or directory
原因就是库的路径没有再环境变量里生效就没有找到这个库。
2) 通过“./test.sh”执行脚本过程中,当前bash进程会派生一个子bash进程去读取和执行脚本,脚本中设置的环境变量也就在该子进程中生效了,你的当前bash进程是没有办法办法使用这些环境变量的。
3) 通过“test.sh”执行脚本。由于“./”表示当前目录,所以加上“./”执行脚本表示在当前目录下找这个脚本执行;如果不加,shell解释器会从$PATH环境变量指定的路径里面查找,而通常$PATH里面是没有"."(表示当前路径)的,所以会报找不到shellscript.sh
4) 由于环境变量不是操作系统内核的东西,所以子进程使用的环境变量都是从父进程继承来的。当我们调用execve派生子进程时,第三个参数就是环境变量,操作系统内核会把这个数据传给新进程,最终execve执行完时,__environ数组的内容跟传入的内容是一致的。
int execve(const char *path, char *const argv[],char *const envp[]);
这样子进程就拥有了和父进程相同的环境变量了。另外main函数有一个隐含的参数就是这个环境变量数组的。
5) 经常被继承的环境变量有这些:
语言环境变量:LANG、LC_*、LC_ALL;
语言环境变量决定字符集的处理方式,设置不正确会导致:字符串转码失败、数据库操作失败、界面显示乱码、……等问题。
时区环境变量:TZ;
TZ环境变量决定着进程所用的时区规则,设置不正确会导致:获取本地时间错误、夏令时跳变错误、UTC/本地时间转换错误、……等问题。
可执行文件查找路径:PATH;
PATH环境变量决定了可执行文件的查找路径,设置不正确会导致进程调用外部可执行程序时,如果没有指定路径信息,可能会执行失败。
动态库查找路径:LD_LIBRARY_PATH;
Windows环境中,PATH环境变量即决定可执行文件的查找路径,也决定动态库的查找路径。但在Solaris/Linux环境中,动态库的查找路径由$LD_LIBRARY_PATH决定。如果设置错误,程序在加载或执行过程中会因为动态库找不到而异常退出(这种退出不会产生core文件)。
根据以上的解释,在执行脚本时就可以清楚地知道要采用哪种方式了。
3. 为什么shell脚本的开头会有#!/bin/bash这一行呢
1) "#!"就是指定了解释器执行,使用./test.sh的方式来执行脚本的时候就需要加上可执行权限,因为此时./test.sh相当于一个可执行程序。
2) "#!/bin/bash"是告诉shell解释器,用什么程序来执行这个文件。现在我们把./test.sh的脚本头改成"#!/bin/abc"再执行,我们可以看到报错如下:
linux-86:/opt/zsmTest # ./test.sh
bash: ./test.sh: /bin/ad: bad interpreter: No such file or directory
然后,再改成#!/bin/cat,执行这个脚本时,就会用cat这个命令执行这个脚本,即打印出脚本的内容,相当于执行cat ./test.sh:
linux-86:/opt/zsmTest # ./test.sh
#!/bin/cat
hadoop_pid=`ps -ef|grep hadoop|grep -v grep|awk '{print $2}'`
echo $ hadoop_pid
4. Shell脚本的调试
当Shell脚本没有执行出想要的结果或者打印出了一个错误的时候,可以调试一下,看看执行的详细过程,shell脚本不会像python那样可以单步跟踪,而是使用bash -x [需要执行的脚本],一次性把脚本的执行信息全部打出来,如下图:
linux-86:/opt/zsmTest # bash -x ./test.sh
++ ps -ef
++ grep mesos-master
++ grep -v grep
++ awk '{print $2}'
+ mesos_master_pid=16036
+ echo 16036
16036
linux-86:/opt/zsmTest #
每一行前面都会有“+”,“+”越多表示该句代码的嵌套层次越深。在脚本执行中,如果有语法错误或者脚本中写的路径找不到等问题,会直接有错误信息打屏的。
5. 为什么shell脚本在执行的时候必须是unix格式的
因为dos格式是以0x0D 0x0A两个字节做行分隔符的,而unix格式则只以0x0A做行分隔符,也就是0x0D会被解释器当成是这一行的内容,所以就会出现命令找不到等各种比较诡异的异常。当脚本已经是DOS格式时,可以先执行dos2unix [需要执行的脚本],然后再执行脚本即可。