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;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 表示空

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nodejs微信小程序+python+PHP个性化书籍推荐系统-计算机毕业设计推荐

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

JavaWeb笔记之前端开发JQuery

一、引言 1.1 概述 jQuery是一个快速、简洁的JavaScript代码库。jQuery设计的宗旨是“Write Less&#xff0c;Do More”&#xff0c;即倡导写更少的代码&#xff0c;做更多的事情。它封装JavaScript常用的功能代码&#xff0c;提供一种简便的 JavaScript操作方式&#xff0c…

宽带阻抗匹配的工程实现-第一步,端口驻波仿真

概要 ADS仿真&#xff0c;Matlab仿真&#xff0c;宽带阻抗匹配&#xff0c;smith圆图。 其实阻抗匹配我工作以来经常说&#xff0c;也经常做&#xff0c;但是基本上都是直接在印制板上进行调试。现在想先用仿真软件直接设计出来&#xff0c;才发现很多东西嘴上说容易&#xf…

JavaScript:函数

JavaScript&#xff1a;函数 函数的作用函数的声明和调用函数声明函数调用函数重复声明 函数传参传参语法参数默认值与参数数量问题传参数量过多传参数量太少参数默认值 函数的返回值函数表达式匿名函数立即执行函数 函数的作用 在我们编程过程中&#xff0c;会出现一种情况&a…

vue-内网,离线使用百度地图(地图瓦片图下载静态资源展示定位)

前言 最近发现很多小伙伴都在问内网怎么使用百度地图&#xff0c;或者是断网情况下能使用百度地图吗 后面经过一番研究&#xff0c;主要难点是&#xff0c;正常情况下我们是访问公网百度图片&#xff0c;数据&#xff0c;才能使用 内网时访问不了百度地图资源时就会使用不了&…

Ps 滤镜:油画

Ps菜单&#xff1a;滤镜/风格化/油画 Filter/Stylize/Oil Paint 油画 Oil Paint滤镜可以轻松地将照片转换为具有经典油画效果的图像。 原图 效果图 Photoshop 23.2 版本重新编写了油画滤镜&#xff0c;以更好地利用 macOS 和 Windows 上的本机 GPU 资源&#xff0c;从而提升了性…

gem5 garnet 拓扑结构之port: NI CPU ROUTER L1 L2

简介 有Crossbar&#xff0c;CrossbarGarnet&#xff0c;Mesh_*&#xff0c;MeshDirCorners_XY&#xff0c;Pt2Pt等拓扑结构&#xff0c;我们主要关注mesh-xy。参考是https://www.gem5.org/documentation/general_docs/ruby/interconnection-network/ MESI TWO LEVEL与 mesh …

SOLIDWORKS Flow Simulation升力仿真分析

仿真飞车起飞和飞机起飞的原理相同,当等质量的空气同时通过机翼上表面和下表面时,会在机翼上下方形成不同流速,空气通过机翼上表面时流速大&#xff0c;压强较小;通过下表面时流速较小,压强大。此时飞车会受一个向上的合力,即向上的升力,空气速度越快,升力越大,当升力大于飞车重…

MySQL概括与SQL分类

文章目录 一、计算机语言二、SQL语言三、数据库系统四、MySQL简介 一、计算机语言 二、SQL语言 三、数据库系统 四、MySQL简介

【OJ比赛日历】快周末了,不来一场比赛吗? #12.23-12.29 #21场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-12-23&#xff08;周六&#xff09; #13场比赛2023-12-2…

基于多反应堆的高并发服务器【C/C++/Reactor】(上)

&#xff08;一&#xff09;初始化服务器端用于监听的套接字 Server.h #pragma once // 初始化监听的套接字 int initListenFd(unsigned short port); Server.c int initListenFd(unsigned short port) {// 1.创建监听的fdint lfd socket(AF_INET, SOCK_STREAM, 0);if(lf…

案例135:基于微信小程序的房屋租赁管理系统的设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

Unity-生命周期

Unity 中,有一个特别重要的知识点,生命周期函数。这些东西全部都是系统定义好的,运行时自动调用,但需要继承 MonoBehaviour 类才能使用。这个类是从 Unity 中创建脚本就自动继承了。 正是因为继承了 MonoBehaviour 这个类,Unity 才能依次调用我们的脚本代码,执行游戏逻辑…

多相机系统通用视觉 SLAM 框架的设计与评估

Design and Evaluation of a Generic Visual SLAM Framework for Multi-Camera Systems PDF https://arxiv.org/abs/2210.07315 Code https://github.com/neufieldrobotics/MultiCamSLAM Data https://tinyurl.com/mwfkrj8k 程序设置 主要目标是开发一个与摄像头系统配置无关…

Linux笔记---文件和目录操作

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux学习 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 命令 ls (List): pwd (Print Working Directory): cp (Copy): mv (Move): rm (Remove): 结语 我的其他博客 前言 学习Linux命令…

vscode打开多个标签页配置

前言 如果其中一个标签的文件没有修改&#xff0c;再打开一个文件时之前的打开的标签页就会被替换掉。 在工作中使用很不方便。 解决办法 文件-首选项--设置 下图取消勾选 取消之后如下 再去打开标签就会一致显示了

Ajax Search Pro Live WordPress网站内容实时搜索插件

点击阅读Ajax Search Pro Live WordPress网站内容实时搜索插件原文 Ajax Search Pro Live WordPress网站内容实时搜索插件是 WordPress 最好的实时搜索引擎插件。高度可定制&#xff0c;具有许多功能和选项&#xff0c;可提供最佳结果&#xff01;用更美观、更高效的搜索引擎替…

55.0/CSS 的应用(详细版)

目录 55.1.1 设计边框样式 55.1.2 调整边框的粗细 55.1.3 边框颜色 55.1.4 复合设置边框 55.2 模块的边距 55.3 模块的内边距 55.4 层的应用 55.4.1 层的建立 55.4.2 浮动——float 55.4.3 清除浮动 55.4.4 层的定位 55.4.5 设置层的溢出——overflow 55.4.6 设置鼠…

数字人解决方案——ER-NeRF实时对话数字人模型推理部署带UI交互界面

简介 这个是一个使用ER-NeRF来实现实时对话数字人、口播数字人的整体架构&#xff0c;其中包括了大语言回答模型、语音合成、成生视频流、背景替换等功能&#xff0c;项目对显存的要求很高&#xff0c;想要达到实时推理的效果&#xff0c;建议显存在24G以上。 实时对话数字人 …

selenium 报错

selenium 报错 开始学自动化测试&#xff0c;&#xff0c;环境配了一天TAT 安装好selenium之后 运行python脚本 # codingutf-8 from selenium import webdriver import timedriver webdriver.Chrome() driver.get("https://www.baidu.com") time.sleep(3) driver.…