侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

详解bash中的退出状态机制

技术  /  管理员 发布于 7年前   496

程序的退出状态

当一个程序结束时会向父进程报告自己的退出状态( exit status ). 通过传递 int 类型的变量给库函数 exit 或系统调用 _exit 可以设置当前程序的退出状态, 在 Linux 中, 通过 WEXITSTATUS 返回的退出状态的值域为 [0, 255] 之间的整数 . 如果传递的值不在这个范围内, 内核会自动帮你强转为 u_int8_t . 通过 waitpid 库函数可以得到子进程的退出状态, 其值存储在参数 wstatus 的低 8 位中.

// 定义在 wait.h 中# define WEXITSTATUS(status)  __WEXITSTATUS (status)// 定义在 waitstatus.h 中/* If WIFEXITED(STATUS), the low-order 8 bits of the status. */#define  __WEXITSTATUS(status)  (((status) & 0xff00) >> 8)

下面这个例子展示了如何使用 waitpid 及相关宏函数获取子进程的退出状态:

#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <sys/types.h>#include <sys/wait.h>#define PARENT_EXIT 10086#define CHILD_EXIT -10int main(){  pid_t pid = fork();  if (pid > 0)  {    int wstatus;    // 父进程等待子进程执行完毕, 用 WUNTRACED 选项追踪已结束的子进程    pid_t child_pid = waitpid(pid, &wstatus, WUNTRACED);    if (WIFEXITED(wstatus))      printf("Child exit status: %d\n", WEXITSTATUS(wstatus));    else      perror("Bad wait status\n");    // 父进程退出    exit(PARENT_EXIT);  }  else if (pid == 0)  {    // 子进程立即退出, 因此需要父进程设置 WUNTRACED    exit(CHILD_EXIT);  }  else  {    // 处理 fork 时出现的错误    perror("fork\n");    exit(EXIT_FAILURE);  }}

编译并运行上例可以得到被强转后的状态码, 我们使用 WIFEXITED 判断等待的子进程是否执行成功, 然后对执行成功子进程使用 WEXITSTATUS 获取其退出状态. 对程序来说, 最终的退出状态就是主进程的退出状态.

> gcc ecitcode.c;./a.out;echo "Parent exit status: $?"Child exit status: 246 # -10 强转为 uint8Parent exit status: 102 # 10086 强转为 uint8

在 POSIX 标准中规定退出状态 0 代表该程序正常退出, 1 代表发生错误, 其他数字由程序自行规定, 因此在 glibc 的 stdlib.h 中仅定义了如下宏:

#define EXIT_FAILURE  1    /* Failing exit status. */#define EXIT_SUCCESS  0    /* Successful exit status. */

程序本身一般会在文档中事先约定每种退出状态代表的退出原因( termination ), 例如在 ls 的帮助文档中:

> ls --help...其他内容...Exit status: # 退出状态 0 if OK, # 正常执行 1 if minor problems # 次要问题, 例如: 无法访问子目录 2 if serious trouble # 严重错误, 例如: 无法访问命令行参数...其他内容...

命令的退出状态

在 bash 中会记录所执行命令的退出状态, 可以通过 $? 获取最近执行的命令的退出状态. bash 自身的退出状态为执行的最后一条命令的退出状态, 也就等价于显式指定 exit $? . 如果没有执行任何命令就退出, 则 bash 的退出状态为 0 , 要注意在 bash 中用 0 表示 true , 用非零表示 false .

# 用 exit 显式指定退出状态> bash> exit 98exit> echo $?98# 什么也不执行则退出状态为 0> bashexit # Ctrl + D 退出> echo $?0# 默认为最后一条命令的退出状态> bash> ecasdecasd: command not foundexit # Ctrl + D 退出> echo $?127

在 bash 中对不同种类命令的退出状态作出如下规定:

内置命令: 由于内置命令执行时不需要启动额外的子进程, 因此需要用返回值模拟退出状态. 每个函数都定义了自己的退出状态, 例如: 内置命令 source 将脚本文件的最后一个命令的返回状态作为命令的返回状态. bash 中所有的内置命令都用退出状态 2 表示用法错误, 例如: 选项错误, 缺少参数.

> cd -+- # 错误的参数bash: cd: -+: invalid optioncd: usage: cd [-L|[-P [-e]] [-@]] [dir]> echo $?2

外部命令: 外部命令的退出状态就是使用 waitpid 得到的子进程的退出状态, 如果子进程在执行过程被编号为 N 的信号所终止, 则得到的退出状态就为 128+N .

Shell 函数: 定义 shell 函数时, 函数名与之前已定义的只读函数名相同则退出状态为 1 , 当发生语法错误则退出状态为 2 . 执行 shell 函数时, 函数中最后执行的一条命令的退出状态就是整个函数的退出状态.

# 二次定义只读函数报错> func () { echo; }> readonly -f func> func; echo $?0> func () { echo poi; }bash: func: readonly function> echo $?1# 定义函数发生语法错误> fune () {aa}bash: syntax error near unexpected token '{aa}'> echo $?2# 函数的退出状态是最后执行的命令的退出状态> funr () { echo; return 6; }> funr; echo $?  # echo 打印的空行6 # return 6 是函数中最后执行的命令

表达式: 使用 ((...)) 或 let 修饰的表达式的退出状态取决于表达式的值, 如果表达式的值为 0 则退出状态为 1 ; 如果表达式的值为非零, 则退出状态为 0 .

> let 0+0; echo $?1 # 表达式值为零> ((7-5)); echo $?0 # 表达式值非零

命令列表: 用 ; , & , && , || 连接命令被称为命令列表, 其中用 && 和 || 连接的命令使用左关联( left associativity )模式执行列表中的命令. 整个命令列表的退出状态为最后一条命令的退出状态. 此外, $( LISTS ) 以及流程控制结构如: for , while 等的返回状态也是结构中的命令列表的退出状态.

# 功能: 能ping通baidu.com则输出 `baidu.com is up` , 否则输出 `baidu.com is down` 。> ping -c1 baidu.com &> /dev/null && echo 'baidu.com is up' || echo 'baidu.com is down'baidu.com is down> echo $?0 # 无论是否能 ping 通, 命令列表的退出状态都等于最后一条命令的退出状态

左关联模式被广泛应用于各种语言的逻辑运算符优化中. 对于逻辑与运算符 && , 以 eq1 && eq2 为例, 只有当两边都为 True 才会返回 True , 因此当 eq1 为 False 时, eq2 不会执行; 对于逻辑或运算符 || , 以 eq1 || eq2 为例, 只要两边有一个 True 就会返回 True , 因此当 eq1 为 True 时, eq2 不会执行。

脚本: 使用 . 或 source 运行脚本文件等同于在当前 bash 中执行代码块, 脚本中最后执行的命令的退出状态就是脚本的退出状态. 使用 ./脚本名 或 bash 脚本名 的方式执行脚本文件等同于执行外部命令, 脚本的退出状态就是外部命令 bash 的退出状态. 如果脚本中最后执行的命令是 exit , 那么使用 . 或 source 执行该脚本文件在执行结束后会退出当前 bash .

后台作业与协作进程: 使用不带选项的 wait 命令可以获得最后一个执行完毕的后台作业的退出状态, 如果使用 wait -n <jobsec> 可以获得指定后台作业的退出状态, 如果作业不存在则退出状态为 127 . 使用 coproc 在 sub shell 中执行的命令的退出状态和后台作业一样可以被 wait 获取, coproc 自身的退出状态始终为 0 .

> { sleep 10; aad; } &[1] 558> wait -n 1[1]+ Exit 127        { sleep 10; aad; }> coproc { sleep 10; aad; }[1] 558> echo $?0 # 这是 coproc 的执行结果> jobs[1]+ Exit 127        coproc COPROC { sleep 10; aad; }

管道命令: 默认情况下, 管道的退出状态取决于管道中最后一条命令的退出状态. 如果设置了 set -o pipefail , 那么只有在管道中的全部命令的退出状态为 0 时, 整个管道的退出状态才为 0 , 否则就是最后一个非零的退出状态. 在管道前添加 ! 符号可以对整个管道的退出状态取反. bash 中的特殊变量 $PIPESTATUS 以数组的形式存储最近执行的前台管道的退出状态, 要注意的是单个命令也会被记录, 也就是说 ${PIPESTATUS[0]} 和 $? 是等价的.

# 管道的退出状态是最后一条命令的退出状态> ps | xxp 2>/dev/null | cat; echo $?0> set -o pipefail > ps | xxp 2>/dev/null | cat; echo $?127 # 设置了 pipefail 因此得到最后一个非零退出状态# 管道中每个命令的退出状态被按顺序记录在数组中> easd 2>/dev/null | ls /nou 2>/dev/null | more 2>/dev/null> echo ${PIPESTATUS[@]}127 2 0# 不带管道符号的单个命令也会被记录> ping asbasdasd 2>/dev/null; echo ${PIPESTATUS[0]}2> ping asbasdasd 2>/dev/null; echo $?2

参考资料

Exit status range
Bash man page

以上就是详解bash中的退出状态机制的详细内容,更多关于bash 退出状态 的资料请关注其它相关文章!


  • 上一条:
    shell实现猜数字小游戏
    下一条:
    详解shell脚本中的case条件语句介绍和使用案例
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 2024.07.09日OpenAI将终止对中国等国家和地区API服务(0个评论)
    • 2024/6/9最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(1个评论)
    • 国外服务器实现api.openai.com反代nginx配置(0个评论)
    • 2024/4/28最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(1个评论)
    • 近期文章
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2017-09
    • 2018-01
    • 2018-07
    • 2018-08
    • 2018-09
    • 2018-12
    • 2019-01
    • 2019-02
    • 2019-03
    • 2019-04
    • 2019-05
    • 2019-06
    • 2019-07
    • 2019-08
    • 2019-09
    • 2019-10
    • 2019-11
    • 2019-12
    • 2020-01
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-12
    • 2024-02
    • 2024-04
    • 2024-05
    • 2024-06
    • 2025-02
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客