目录
遇到错误退出
简介和使用
set -e #脚本里面有返回值非0命令/运行失败的命令 就退出
set -u #如果遇到不存在的变量,就退出
sh -x my.sh 回整个脚本都输出调试信息,太多了
使用set -x开启某一段的调试信息:
set -x 开始调试;
set +x 结束调试;
-x还有另一种写法-o xtrace。
set -o xtrace
set -e 的陷阱
1,管道命令
set -o pipefail
set -e有一个例外情况,就是不适用于管道命令。
就是多个子命令通过管道运算符(|)组合成为一个大的命令。Bash 会把最后一个子命令的返回值,作为整个命令的返回值。即只要最后一个子命令不失败,管道命令总是会执行成功,因此它后面命令依然会执行,set -e就失效了。
例子:假如foo命令不存在
foo | echo a
foo是一个不存在的命令,但是foo | echo a这个管道命令会执行成功,导致脚本会继续往后执行
#!/bin/bash
set -e
for species in `something`;do
...
for id in `something`;do
cmd1 | cmd2 | grep sth
done | cmd3 | cmd4 > somefile
done
cmd5
内层循环某一次grep失败,会导致整个内层循环退出,而由于内层循环与后面的管道形成了一个整体,这个整体的最后一个命令(重定向到somefile文件)不会失败,所以这个整体不会触发ERREXIT。外层循环可以顺利运行,遍历整个列表
2, grep匹配不到会导致退出
#!/usr/bin/env bash
set -e
PID=$(ps -ef | grep "进程标识" | grep -v grep | awk '{print $2}')
echo "pid is: "$PID
如果没有相应的进程会因为第二个 grep :grep "进程标识"匹配不到,退出码 $? 为 1,set -e导至脚本退出,没有执行echo输出
解决办法:使用bash的分组命令功能:Grouping Commands
#!/usr/bin/env bash
set -e
PID=$(ps -ef | { grep "进程标识" || true; } | { grep -v grep || true; } | awk '{print $2}')
echo "pid is: "$PID
参考:linux shell set -e grep 匹配不到导致脚本退出问题https://blog.csdn.net/zswspock/article/details/119245835
下面的也能使得set -e 失效
command | grep -r ${str} | tee a.log
tee a.log 可以改变命令的执行后的返回值,而不改变命令本身的执行结果
shell退出时执行|接收信号|trap
(摘自:https://www.jianshu.com/p/07220a5d855a)
用途:
1,脚本退出时执行清理等
2,项目中的升级脚本可能耗时很长,没有输出像卡住,部署人员直接CTRL+C停掉升级脚本造成破坏。可以使用Shell的内建命令trap来忽略SIGINT这些信号,保证升级不会中断。
trap介绍
功能就是设置信号处理行为,trap的格式如下:
trap [-lp] [[arg] sigspec ...]
arg
可以是shell命令或者自定义函数sigspec
定义在<signal.h>中的信号名或者数值,SIG前缀是可选的,大小写不敏感,可以一个或多个
例子:
trap "echo 123" SIGINT #设置ctrl+C 时执行echo 123
trap "echo 123"
EXIT#设置exit 退出时执行echo 123,
EXIT也可以替换为0
trap "echo 123"
RETURN#
在函数返回时,或者.
和source
执行其他脚本返回时时执行echo 123
trap "echo 123"
DEBUG#
在运行脚本前执行"echo 123"
,但对于函数仅在函数的第一条命令前执行一次
trap "echo 123"
ERR#
在命令结果为非0时,执行trap设置的命令:echo 123
#!/bin/bash
foo()
{
echo "foo 1"
echo "foo 2"
}
trap "echo 123" DEBUG
foo
执行结果
123 # 在函数前执行一次
foo 1
foo 2
列出所有信号的数值和名字
trap -l
:列出所有信号的数值和名字,类似于kill -l
[root@localhost ~]# trap -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
trap -p
:列出通过trap设置的信号处理命令。
[root@localhost ~]# trap "echo INT" INT
[root@localhost ~]# trap -p INT
trap -- 'echo INT' SIGINT
trap "" sigspec
:忽略sigspec指定的信号
trap "do something" sigspec
:收到sigspec指定的信号时,do some thing后,继续执行后续命令。
trap sigspec
ortrap - sigspec
:恢复sigspec指定的信号的默认行为
trap的注意事项
- trap可以在收到信号前的任意位置设置,并非需要在脚本的第一行,但是shell是按照顺序执行语句的,不会优先执行trap
#!/bin/bash
trap -p INT # 不输出任何信息
trap "echo get signal" INT
- 在函数中设置trap,也是全局生效的
- 对于同一个信号,只有最后一次trap生效
- trap只在本进程内有效,它的子进程不会继承trap的设置。
main.sh - 如果子进程阻塞着,当通过kill直接杀死父进程时,只有等到子进程退出,父进程才会处理信号。kill -2 杀掉以下脚本的进程,此时需要等待10秒后,才会输出"get signal"。因为CTRL+C的信号是发送给进程组,此时sleep进程被INT信号中断了,所以立即输出了"get signal",可以用Kill -2 发送信号到进程组达到一样的效果。
#!/bin/bash
trap "get signal" INT
sleep 10
还有一个变通的方法就是把sleep放在后台进行,并用wait等待,wait是shell的内建命令,会被本进程收到的信号直接打断,此时sleep是继续在后台执行的。
#!/bin/bash
trap "get signal" INT
sleep 10 & wait $!
- 处理SIGINT或者SIGQUIT时,需要特别注意。比如下面的脚本,CTRL+C后只是中断了一次sleep,当信号处理结束后,又会进入下一次sleep,这可能并不符合预期。
#!/bin/bash
trap "echo get signal" INT
sleep 10
sleep 10
需要在处理信号中,将信号处理恢复到默认,并以INT信号再次杀掉自己
#!/bin/bash
trap "echo get signal;trap - INT;kill -s INT "$$" " INT
sleep 10
sleep 10