shell 的错误处理和调试方法

news2025/1/8 3:33:55

简介

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

        

目录

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;do
    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 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=$3
    local current_time="$(date '+%Y-%m-%d %H:%M:%S')"

    # 如果第3个字符为0,表示失败
    if [ ${result} -eq 0 ];then
        result="FAILED"
    # 如果第3个字符为1,表示成功
    elif [ ${result} -eq 1 ];then
        result="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" 1

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

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

结果如下:

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

这种方法怎么去应用呢?

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

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

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

        

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

        

1.2. debug调试

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

# 监控磁盘大小
MonitorDisk(){
    path="/home/yt"
    while true;do
        local 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 10
    done
    }

# 监控内存大小
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 10
        wait
    done
    }
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 ];then
    echo "正确"

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
    密码
    sync
    echo 3 > /proc/sys/vm/drop_caches
EOF

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

func(){
    su root <<-EOF
        密码
        sync
        echo 3 > /proc/sys/vm/drop_caches
    EOF
    }
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 ];then
    echo "状态码为0,上一条命令执行成功!"
else
    echo "状态码非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.coloradmin.cn/o/1327558.html

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

相关文章

在 Windows 搭建 SVN 服务

近公司给配了台新电脑&#xff0c;配置挺主流的&#xff0c;但是不舍得抛弃自己的旧电脑&#xff08;原配嘛&#xff0c;这叫贪新不舍旧&#xff09;&#xff0c;于是打算在新电脑上只作开发环境&#xff0c;然后旧电脑作为版本库&#xff0c;这样保存版本的时候可以直接上传到…

vue项目npm run build报错npm ERR! missing script: build(已解决)

vue项目npm run build报错npm ERR! missing script: build&#xff08;已解决&#xff09; 错误描述&#xff1a; 今天准备打包vue项目上线是出现下列错误&#xff1a; 找了很多解决方法都不行&#xff0c;最后打开自己的package.json文件发现&#xff1a;build后面多了个&a…

idea SpringBoot项目 Run Dashboard 多个启动类分组展示 失效

idea SpringBoot项目 Run Dashboard 多个启动类分组展示 想要的显示格式 失效的显示格式, 没有按照分组进行平铺展示 解决方案 勾选 展示服务树 参考文章: IDEA 打开Run Dashboard 分组启动 https://www.cnblogs.com/hanjun0612/p/10917689.html idea springBoot项目Run…

深入理解网络 I/O:mmap、sendfile、Direct I/O

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

论文中公式怎么降重 papergpt

大家好&#xff0c;今天来聊聊论文中公式怎么降重&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 论文中公式怎么降重 一、引言 在论文撰写过程中&#xff0c;公式是表达学…

Spring Environment 注入引起NPE问题排查

文章目录 背景原因分析1&#xff09;Spring Aware Bean 是什么&#xff1f;2&#xff09;从 Spring Bean 的生命周期入手 解决方案 背景 写业务代码遇到使用 Spring Environment 注入为 null 的情况&#xff0c;示例代码有以下两种写法&#xff0c;Environment 实例都无法注入…

机器视觉系统选型-图像清晰度

 确定图像中所要检测的部分处于清晰的焦距之内  相机分辨率和镜头解析度较好的匹配&#xff0c;考虑镜头的景深等 TIPS&#xff1a; 每一款镜头都有一定的“焦距”&#xff0c;每一款镜头都有一定光圈范围&#xff0c;也有一定的景深&#xff1b; 缩小光圈可以加大景深&am…

Spring Boot学习随笔- 拦截器实现和配置(HandlerInterceptor、addInterceptors)、jar包部署和war包部署

学习视频&#xff1a;【编程不良人】2021年SpringBoot最新最全教程 第十三章、拦截器 拦截器 &#xff1a;Interceptor 拦截 中断 类似于javaweb中的Filter&#xff0c;不过没有Filter那么强大 作用 Spring MVC的拦截器是一种用于在请求处理过程中进行预处理和后处理的机制。拦…

ssm420基于JavaEE的企业人事管理信息系统的设计与实现论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本企业人事管理信息系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

如何本地搭建Splunk Enterprise平台并公网访问管理界面

文章目录 前言1. 搭建Splunk Enterprise2. windows 安装 cpolar3. 创建Splunk Enterprise公网访问地址4. 远程访问Splunk Enterprise服务5. 固定远程地址 前言 Splunk Enterprise是一个强大的机器数据管理平台&#xff0c;可帮助客户分析和搜索数据&#xff0c;以及可视化数据…

如何选择适合的UI自动化测试工具

随着软件开发项目的复杂性增加&#xff0c;UI自动化测试成为确保应用程序质量的关键步骤之一。然而&#xff0c;在选择UI自动化测试工具时&#xff0c;开发团队需要考虑多个因素&#xff0c;以确保选取的工具适用于项目需求并提供可靠的测试结果。 1. 了解项目需求 在选择UI自动…

计算机网络实验速成

目录 网络实验速成 自动连接类型&#xff1a; 指示灯状态说明&#xff1a; 显示接口&#xff1a; 放置注释信息&#xff1a; 配置计算机&#xff1a; 同理&#xff0c;配置服务器&#xff1a; 配置路由器&#xff1a; router0 配置&#xff1a; router1 配置&…

Hardhat环境搭建(六)---无需翻墙

Hardhat环境搭建 官方地址 node环境 npm环境 git环境 安装hardhat npm init npminit是什么 在node开发中使用npm init会生成一个pakeage.json文件&#xff0c;这个文件主要是用来记录这个项目的详细信息的&#xff0c;它会将我们在项目开发中所要用到的包&#xff0c;以…

用户管理第2节课-idea 2023.2 后端一删除表,从零开始---【本人】

一、清空model文件夹下&#xff0c;所有文件 1.1.1效果如下&#xff1a; 1.1代码内容 package com.daisy.usercenter.model;import lombok.Data;Data public class User {private Long id;private String name;private Integer age;private String email; }二、清空mapper文件…

windows下使用gtest

我是在window下使用clion来写c的&#xff0c;最近学习了gtest&#xff0c;中间遇到了一些问题&#xff0c;记录一下。 整体目录 先看一下目录结构 两个测试case&#xff0c;前面就有运行的标志&#xff0c;直接点击就能运行 具体的代码 CMakeLists.txt cmake_minimum_req…

HarmonyOS 学习

语言是 ArkTS UI框架是ArkUI TypeScript 1、基础类型 布尔类型&#xff1a;boolean 浮点型&#xff1a;number 字符串&#xff1a;string 数组&#xff1a;数组&#xff1a;number[] 数组泛型&#xff1a;Array<number> 元组&#xff1a;let x&#xff1a;[strin…

版本化数据库管理工具Flyway介绍和Spring Boot集成使用

文章目录 核心功能如何使用 Flyway最佳实践Spring Boot使用 Flyway 是一个版本化数据库管理工具&#xff0c;用于跟踪、管理和应用数据库的变化。它非常适合在团队开发环境中使用&#xff0c;其中多个人员可能会在数据库结构进行更改。Flyway 通过版本控制可以帮助你确保所有人…

yocto系列讲解[实战篇]93 - 添加Qtwebengine和Browser实例

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 概述集成meta-qt5移植过程中的问题问题1:virtual/libgl set to mesa, not mesa-gl问题2:dmabuf-server-buffer tries to use undecl…

红队打靶练习:DIGITALWORLD.LOCAL: DEVELOPMENT

信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:69:c7:bf, IPv4: 192.168.12.128 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.12.1 00:50:56:c0:00:08 …

Docker 学习总结(80)—— 轻松驾驭容器,玩转 LazyDocker

前言 LazyDocker 是一个用户友好的命令行工具,简化了 Docker 的管理。它能够通过单一命令执行常见的 Docker 任务,如启动、停止、重启和移除容器。LazyDocker 还能轻松查看日志、清理未使用的容器和镜像,并自定义指标。 简绍 LazyDocker 是一个用户友好的 CLI 工具,可以轻…