shell 的错误处理和调试方法

简介

    在我们写代码过程中,一般有两个阶段:调试阶段和试运行阶段。在调试阶段我们希望尽可能的输出日志,方便在出错的时候快速定位问题。在试运行阶段希望将日志标准化,且有些错误的日志是在预期内不想展示的时候如何处理,这篇基础文章将介绍这两个阶段如果有效的节约编程时间。

        

目录

1. 脚本调试

1.1. 日志输出

1.2. debug调试

2. 运行shell脚本的异常报错

2.1. 找不到命令

2.2. 语法缺少结束符

2.3. 部分命令无法执行(巨坑)

3. 错误处理

3.1. 异常状态码

3.2. 正常、异常日志重定向


        

1. 脚本调试

我们在编写脚本时,调试时需要用到2种方法:

  1. 每个任务点输出有效日志;
  2. 出错时怎样查看详细信息。

1.1. 日志输出

如何通过输出日志达到调试的目的呢?

我们可以使用 echo 或者 printf 命令来输出当前的任务情况。例如:其中一个任务为监控磁盘大小

path="/home/yt"
while true;dosize=$(df -h ${path} |awk 'NR==2{print $4}')echo "`date '+%Y-%m-%d %H:%M:%S'` [INFO] The available disk space is ${size}"sleep 10
done

在代码中,我们使用了时间+类型+信息的方式汇报结果,这可以使得我们对某个任务的执行时间和做的事情有很清晰的了解。

        

如果觉得每次输出日志都需要加一下时间之类的东西很麻烦,不妨试试用函数封装一个方法

PrintLog(){local str_type="$1"local str="$2"local result="$3"local current_time="$(date '+%Y-%m-%d %H:%M:%S')"printf "${current_time} [${str_type}] %-50s ${result}\n" "${str}"}
  • str_type:日志类型(自定义:INFO、WARNING、ERROR、DEBUG等)。
  • str:自定义日志信息。
  • result:最终结果(自定义:SUCCEED、FAILED等)。

        

准备好一个简易版的日志输出方法,来检验一下

# 函数名  "类型"  "输出的字符"  "最终结果"
PrintLog "INFO" "Check the running IP address" "SUCCEED"
PrintLog "INFO" "Check the system configuration" "FAILED"

按照预期输出了时间、类型、字符串、结果。

        

但这还不够,我们再来改进一下:

  1. result 每次输入 SUCCEED 或 FAILED 太麻烦了,直接用 1 和 0 替代。
  2. result 需要支持 不输出结果、自定义结果、0或1选项。
  3. 如果 result 为 FAILED,则退出程序。
PrintLog(){local str_type="$1"local str="$2"local result=$3local current_time="$(date '+%Y-%m-%d %H:%M:%S')"# 如果第3个字符为0,表示失败if [ ${result} -eq 0 ];thenresult="FAILED"# 如果第3个字符为1,表示成功elif [ ${result} -eq 1 ];thenresult="SUCCEED"fi# 输出日志信息printf "${current_time} [${str_type}] %-50s ${result}\n" "${str}"# 如果result="FAILED",则退出程序[ ${result} == "FAILED" ] && exit 1}

优化代码后再假装执行3个任务

echo "============= 执行任务1 ============="
PrintLog "INFO" "Perform Task 1" 1echo "============= 执行任务2 ============="
PrintLog "INFO" "Perform Task 2" 0echo "============= 执行任务3 ============="
PrintLog "INFO" "Perform Task 3" 1

结果如下:

在执行任务2时,指定 result 为0(表示异常),所以shell在执行完第2个任务后自动终止脚本。

这种方法怎么去应用呢?

# 执行一个ls命令
ls abcd
# 如果这个命令执行失败,那么输入指定日志后退出脚本
[ $? -ne 0 ] && PrintLog "ERROR" "Run the ls command" 0echo "============= 执行任务1 ============="
PrintLog "INFO" "Perform Task 1" 1

通过 $? 判断上一个命令是否正常,如果不正常则输出错误信息并退出

        

一般情况下,脚本中都含有多个任务,这些任务一般都由函数封装。对使用者来说:每个任务输出一行信息就行,对我们编写者来说:能少写一行就少写一行。所以,在日志输入上,主任务中输出一行有效日志即可。当发现某个主任务出现了异常但没找到问题时,我们可以继续在出现问题这个函数中输出更详细的日志。

        

1.2. debug调试

在 shell 一般使用 bash -x 来调试脚本。一般情况下,我们基本可以通过系统本身抛出的错误来迅速找到代码的问题,但有一些问题是无法通过系统提示定位问题的。比如:进程卡住

# 监控磁盘大小
MonitorDisk(){path="/home/yt"while true;dolocal size=$(df -h ${path} |awk 'NR==2{print $4}')echo "`date '+%Y-%m-%d %H:%M:%S'` [INFO] The available disk space is ${size}"sleep 10done}# 监控内存大小
MonitorMemory(){while true;do# 将监控磁盘作为子进程,同时监控两种状态MonitorDisk &local mem_free=$(free -h |awk 'NR==2{print $4}')echo "`date '+%Y-%m-%d %H:%M:%S'` [INFO] The remaining memory is ${mem_free}"sleep 10waitdone}
MonitorMemory

这里写了一个错误的示例,将监控磁盘放到了监控内存里面,并且使用 wait 等待,结果如下:刚开始两种同时监控,但后面只监控到了磁盘

当出现这种不符合预期的情况,系统也没有报错,那么我们需要查看 debug 日志:

在这张图片中,分别出现了2种不同类型的日志:带+号、不带+号。

  • 带+符号:表示脚本中的代码
  • 不带+号:表示输出的日志信息

在这些带+号的代码中,又分别会出现1个、2个、3个、n个,这些实际上是级别表示:

  • +:一级执行级别(顶层执行的命令,通常是整个脚本中的命令)。
  • ++:二级执行级别(通常用于嵌套在一级执行级别命令中的命令)。
  • +++:三级执行级别(更深度嵌套的代码或执行流程)。

我们来看一下这个脚本的信息

先看红框,这是两个任务的执行信息, 标注的1、2、3、4分别是它们的执行流程。

1:执行的内存监控

2:执行的磁盘监控

3:执行的磁盘监控

4:执行的磁盘监控

我们发现执行内存监控后就一直执行磁盘监控,而后内存监控没再工作。所以我们往1~3的中间找找其他日志,发现在2后面出现一个 wait 命令,使用 wait 后会持续等待子进程结束,所以,这个脚本的问题就在于 wait ,我们重新将函数和 wait 放入最后一行。

MonitorDisk &
MonitorMemory &
wait

最终结果:符合预期

        

2. 运行shell脚本的异常报错

shell 的运行有2个点需要注意:

  1. 如果脚本中出现语法不正确时并不会在执行前检查,而是在执行过程中发现语法错误后自动退出;
  2. 如果脚本中语法正确,但执行过程中的"命令"出错不会退出。

针对这两点我们来看看语法问题应该如何处理,有哪些坑需要注意。

  • Linux中可以通过命令 shellcheck [脚本] 来检查脚本语法,这里就不对这个命令进行说明了。

        

2.1. 找不到命令

  • 出现找不到命令的错误不会终止脚本,会继续执行。
echo "=========开始运行脚本========="
a    # 执行一个错误的命令
echo "=========结束运行脚本========="

我们设置了 3 条命令,开始和结尾的命令是正常的,中间命令是不存在的,看一下结果:

运行结果如下:

  • 【正常】执行第1条命令
  • 【异常】执行第2条命令
  • 【正常】执行第3条命令

中间出现了异常的命令,shell不会终止脚本,输出对应信息后继续往下执行。输出的信息:

  • 【脚本路径】【异常行数】【异常命令】【异常提示】

正常的处理流程就是:

  1. 查看异常提示是什么
  2. 查看异常行数,vim +[行号] [脚本] 检查问题
  3. 最后修改问题

        

2.2. 语法缺少结束符

  • 出现语法错误后终止脚本,但不会提前检查。
echo "=========开始运行脚本========="if [ 1 -eq 1 ];thenecho "正确"echo "=========结束运行脚本========="

这里可以看到脚本的第1行正常执行,第2行的 if 判断因为语法问题而报错,报错以后直接退出,后面的代码不再执行。箭头处系统给出的报错文件是第9行,而我们文件总共才8行,哪来的第9行。我去查了一下资料没查到,有懂的小伙伴请评论区留言。所以我去总结了一下哪些情况会这样:

  • if 判断缺少结束符 fi
  • for 循环缺少结束符 done
  • while 循环缺少结束符 done
  • case 缺少结束符不会这样,会在缺少的那一行报错。

总的来说,只要看到报错的行数大于文件总行数,并且只输出了语法错误 没有具体的错误信息,那基本就是结尾符的问题了。

        

理解了这个异常是什么导致的以后,下一个问题来了,当我们脚本很大 又没有用函数封装,利用单行注释去调试又太慢,怎么办?

举个例子,这里有很多个 if

执行结果是这样的:前面正常执行,后面语法错误,抛出异常400行

我们用最简单的方法:过滤查找(缩小范围)

grep -nE "if|fi" [文件名]

使用 grep 输出包含 if 和 fi 的行号和信息,手动去检查,如果 if 下面缺少 fi 基本就能确定是哪行

13 和 17 行这里出现了2个 if ,一般嵌套很少有 if 嵌套 if,即使有也很少。通过这里我们发现 13 的 if 没有结束符,如果代码类似我这种情况2s搞定,如果比较复杂按缩进排查就行。

        

2.3. 部分命令无法执行(巨坑)

为什么说这个时巨坑,因为执行的时候不报错,bash -x 发现不了问题。这是之前在写一个shell过程中,拷贝代码过来导致中间一部分命令无法执行,找了半个小时才发现罪魁祸首是 EOF。

通过 <<EOF 可以在脚本中创建一个文本块,并将其传递给命令或程序。我的目的是使用root用户清理缓存

#将EOF中的文本传递给 su root 命令
su root <<-EOF密码syncecho 3 > /proc/sys/vm/drop_caches
EOF

这样看起来没毛病吧,但我是函数,所以多加了一个 tab,再来看看效果

func(){su root <<-EOF密码syncecho 3 > /proc/sys/vm/drop_cachesEOF}
func

在 EOF 前方加上 - 符号可以忽略 tab,所以这种写法是没问题的。问题出在我是拷贝过来的,拷贝过来的 tab 就变成了空格,这种情况加 - 符号也无效,所以结尾的 EOF 那里也就无效了。本来是应该抛出这个错误:

但由于脚本中拷贝了多个EOF,导致它没有抛出异常,而是直接忽略了中间那部分代码。这种情况使用 bash -x 直接不显示中间那部分函数,压根儿 不执行。

所以啊,在写EOF时一定不要用空格,不要拷贝!!!

        

3. 错误处理

3.1. 异常状态码

Linux 每执行一个任务或命令时都会返回一个状态码(范围 0~255),使用 $? 获取

0    :表示执行成功。
1-125:命令或脚本执行的常规错误代码。
126  :命令找到但无法执行。
127  :命令未找到。
128+ :通常表示命令或脚本因接收到异常信号而终止。

所以我们在判断一个命令是否执行成功,只需要使用 $?。例如执行一个异常的命令

返回的状态码非 0

        

再来执行一个正常的命令

返回状态码为 0

        

所以当我们判断一个命令是否执行成功时,可以这样写

ls "abc"
if [ $? -eq 0 ];thenecho "状态码为0,上一条命令执行成功!"
elseecho "状态码非0,上一条命令执行失败!"
fi

        

3.2. 正常、异常日志重定向

在 shell 中,系统抛出的日志分为正常和异常两种。当我们对某个命令所返回的结果重定向到另一个文件中时,系统会自动判断:如果命令执行成功则可以重定向到某个文件,如果命令执行失败则无法重定向到某个文件,直接输出到屏幕。

可以看到,执行成功的命令结果是可以重定向到文件 tmp.txt 中,而执行失败的结果是无法重定向到 tmp.txt 中。

        

如果我们必须将任务的执行结果输出到一个文件时(不论正常还是异常),那么可以通过 1 和 2 来指定

  • 0 :表示标准输入 stdin,通常对应于键盘输入。
  • 1 :表示标准输出 stdout,通常对应于命令或脚本的正常输出。
  • 2 :表示标准错误输出 stderr,通常用于输出命令或脚本的错误信息。

使用 2>&1 将标准错误输出 stderr 重定向到标准输出 stdout 上,所以可以输出到文件。

        

如果我们希望将错误日志和正常日志分开存放应该怎么处理呢?

使用 1 和 2 分开存放,将正常的日志追加到 info.log,将异常的日志追加到 err.log

        

如果不想输出日志又怎么处理呢?

直接将其输出为空,/dev/null 表示空

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/237832.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1. 线程概述、线程和进程、 并发和并行、多线程的优势 、程序运行原理 、主线程、 线程的 6 种状态

文章目录 1. 线程概述1.1 线程和进程1.2 并发和并行1.3 多线程的优势1.4 程序运行原理1.5 主线程 1.6 线程的 6 种状态 1. 线程概述 1.1 线程和进程 ​ 进程是处于运行过程中的程序&#xff0c;并且具有一定的独立功能&#xff0c;进程是系统进行资源分配和调度的一个独立单位…

高校/企业如何去做数据挖掘呢?

随着近年来人工智能及大数据、云计算进入爆发时期&#xff0c;依托三者进行的数据分析、数据挖掘服务已逐渐成为各行业进行产业升级的载体&#xff0c;缓慢渗透进我们的工作和生活&#xff0c;成为新时代升级版的智能“大案牍术”。 那么对于多数企业来说&#xff0c;如何做数据…

将遗留系统分解为微服务:第 2 部分

在当今不断发展的技术环境中&#xff0c;从整体架构向微服务的转变对于许多企业来说都是一项战略举措。这在报销计算系统领域尤其重要。正如我在上一篇文章第 1 部分应用 Strangler 模式将遗留系统分解为微服务-CSDN博客中提到的&#xff0c;让我们探讨如何有效管理这种转变。 …

Kylin的工作原理及使用分享:构建大数据分析之塔

学习目标&#xff1a; 了解Kylin的工作原理和基本概念理解Kylin在大数据分析中的作用和价值学会使用Kylin进行数据建模、数据预处理和查询 学习内容&#xff1a; 什么是Kylin&#xff1f; Kylin是一个开源的分布式分析引擎&#xff0c;专注于大数据的实时多维分析。它能够通过…

Appium Server 启动失败常见原因及解决办法

Error: listen EADDRINUSE: address already in use 0.0.0.0:4723 如下图&#xff1a; 错误原因&#xff1a;Appium 默认的4723端口被占用 解决办法&#xff1a; 出现该提示&#xff0c;有可能是 Appium Server 已启动&#xff0c;关闭已经启动的 Appium Server 即可。472…

Alien Skin Exposure 7汉化破解版下载 V 7.1.0.214 中文注册版

软件介绍 Alien Skin Exposure 7是一款超好用的PS胶片效果调色滤镜&#xff0c;它为数码照片提供胶片的曝光&#xff0c;还包括模仿胶片的颗粒感&#xff0c;并且可以控制胶片颗粒的分布&#xff0c;能够帮助用户对图片进行更好的处理&#xff01; 软件特色 1、支持RAW格式&a…

node-red:使用node-red-contrib-amqp节点,实现与RabbitMQ服务器(AMQP)的消息传递

node-red-contrib-amqp节点使用 一、简介1.1 什么是AMQP协议?1.2 什么是RabbitMQ? -> 开源的AMQP协议实现1.3 RabbitMQ的WEB管理界面介绍1.3 如何实现RabbitMQ的数据采集? -> node-red 二、node-red-contrib-amqp节点安装与使用教程2.1 节点安装2.2 节点使用2.2.1 amq…

Ultra Mobile PayGO购买充值激活

一、前言 Ultra Mobile PayGO免费无限拨打 80 多个国际目的地&#xff0c;还可以向 190 多个国际目的地发送短信。在现在ai智能时代&#xff0c;我自己也需要一张这样的卡&#xff0c;今天用fomepay的虚拟卡激活了这张电话卡&#xff0c;根据需要按套餐购买。 二、点击申请fom…

vue-pure-admin源码解读与使用

vue-pure-admin 全面使用ESMVue3ViteElement-PlusTypeScript编写的一款后台管理系统&#xff08;兼容移动端&#xff09;,目前斩获11.5k个star。 界面构成 主题Layout的组成 左边sidebar由Vertical组件定义tab标签栏由layoutHeader组件定义中间Body由appMain组件定义 为何点…

matlab 点云最小二乘拟合空间直线(PCA法)

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。爬虫网站自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法原理 见:matlab 点云最小二乘拟合空间直线。 二、代码实现 clc;clear; %% ----

【数字图像处理】实验三 图像增强

图像增强 一、实验内容&#xff1a; 1&#xff0e; 熟悉和掌握利用Matlab工具进行数字图像的读、写、显示等数字图像处理基本步骤。 2&#xff0e; 熟练掌握各种图像增强的基本原理及方法。 3&#xff0e; 能够从深刻理解图像增强&#xff0c;并能够思考拓展到一定的应用领域。…

VM Group

在复杂方案中模块过多可能造成查看或修改方案时存在视觉混乱&#xff0c;不够直观。此时可利用Group模块进行模块整合&#xff0c;同时Group模式也兼容循环的功能&#xff0c;如下图所示。 双击Group模块可进入Group内部&#xff0c;如下图所示。 在Group模块单击 可设置输入、…

两款超好用的视频翻译软件,适合两种不同场景,必有一款适合你

今天给大家推荐2个视频翻译工具​&#xff0c;分别是&#xff1a; TransDuckYouTube中文配音 这两款工具的核心功能非常类似&#xff0c;核心提供一个视频语言翻译的能力。比如&#xff1a;你要看一个英文视频&#xff0c;它可以帮助你将这个英文视频变为中文视频&#xff0c…

软件系统质量保证计划书

本计划描述了信息系统项目质量保证工作相关的一些情况&#xff0c;是软件质量保证过程和方针在项目中的具体实施计划。 计划中阐述了质量保证工作的基本目标&#xff1b;项目的基本情况&#xff1b;质量保证工作所需的资源&#xff1b;质量保证的主要工作&#xff1b;工作量估算…

postman转参的是“” mybatis将“”当0处理问题

Mybatis中 Integer 值为0时&#xff0c;默认为空字符串的解决办法-蒲公英云 传0当成了"" 解决&#xff1a; mybatis当传入数据类型为Int时并且值为0时,会判断为空字符串-腾讯云开发者社区-腾讯云 https://www.cnblogs.com/shenhaha520/articles/16301304.html 传…

试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)

试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B) c#调用动态库是报错 目前平台改为x64

物业服务投诉反馈建议建议二维码

为高效处理物业方面的投诉问题&#xff0c;进一步提升居住品质。凡尔码平台推出“二维码”便民投诉、反馈方式&#xff0c;如有群租扰民、占用堵塞消防通道或私拉乱建等问题&#xff0c;可以立即扫码或进入“凡尔码”小程序进行投诉或反馈。 如电梯出现故障物业服务企业未及时维…

基于Java SSM框架实现咖啡馆管理系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现咖啡馆管理系统演示 摘要 2021是网络科技的时代 &#xff0c;众多的软件被开发出来&#xff0c;给客户带来了很大的选择余地&#xff0c;而且客户越来越追求更个性的需求。在这种时代背景下&#xff0c;客户对咖啡馆管理系统越来越重视&#xff0c;使更好…

深入理解qs库:简化你的工作流程

前言 在 vue 开发中&#xff0c;处理 url 查询字符串是一个常见的任务。qs 库是一个流行的工具&#xff0c;可以帮助我们轻松地处理 url 查询字符串的编码和解码。本文将介绍 qs 库的基本用法&#xff0c;并结合实例演示帮助你更好地理解和应用这个实用的工具。 一、qs 是什么&…

5. 结构型模式 - 外观模式

亦称&#xff1a; Facade 意图 外观模式是一种结构型设计模式&#xff0c; 能为程序库、 框架或其他复杂类提供一个简单的接口 问题 假设你必须在代码中使用某个复杂的库或框架中的众多对象。 正常情况下&#xff0c; 你需要负责所有对象的初始化工作、 管理其依赖关系并按正确…