shell 中的 set -e 导致的退出问题

目录

set命令

set -e

set -o pipefail

set -u

set -x

grep 

退出码

-c参数

grep范例

范例

范例1

范例2

解决方法



set命令

参见:http://www.ruanyifeng.com/blog/2017/11/bash-set.html

Bash 执行脚本的时候,会创建一个新的 Shell。

# bash script.sh

上面代码中,script.sh是在一个新的 Shell 里面执行。这个 Shell 就是脚本的执行环境,Bash 默认给定了这个环境的各种参数。set命令用来修改 Shell 环境的运行参数,也就是可以定制环境。一共有十几个参数可以定制,官方手册有完整清单,本文介绍其中最常用的四个。顺便提一下,如果命令行下不带任何参数,直接运行set,会显示所有的环境变量和 Shell 函数。

 

set -e

如果脚本里面有运行失败的命令(返回值非0),Bash 默认会继续执行后面的命令。

#!/usr/bin/env bash
foo
echo bar

上面脚本中,foo是一个不存在的命令,执行时会报错。但是,Bash 会忽略这个错误,继续往下执行。

$ bash script.sh
script.sh:行3: foo: 未找到命令
bar

可以看到,Bash 只是显示有错误,并没有终止执行。
这种行为很不利于脚本安全和除错。实际开发中,如果某个命令失败,往往需要脚本停止执行,防止错误累积。这时,一般采用下面的写法。

command || exit 1
上面的写法表示只要command有非零返回值,脚本就会停止执行。
如果停止执行之前需要完成多个操作,就要采用下面三种写法。

# 写法一
command || { echo "command failed"; exit 1; }

# 写法二
if ! command; then echo "command failed"; exit 1; fi

# 写法三
command
if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi

另外,除了停止执行,还有一种情况。如果两个命令有继承关系,只有第一个命令成功了,才能继续执行第二个命令,那么就要采用下面的写法。

command1 && command2

上面这些写法多少有些麻烦,容易疏忽。set -e从根本上解决了这个问题,它使得脚本只要发生错误,就终止执行。

#!/usr/bin/env bash
set -e

foo
echo bar

另外:

set +e表示关闭-e选项,

set -e表示重新打开-e选项。

还有一种方法是使用command || true,使得该命令即使执行失败,脚本也不会终止执行。

#!/bin/bash
set -e

foo || true
echo bar
上面代码中,true使得这一行语句总是会执行成功,后面的echo bar会执行。

set -o pipefail

set -e有一个例外情况,就是不适用于管道命令。
所谓管道命令,就是多个子命令通过管道运算符(|)组合成为一个大的命令。Bash 会把最后一个子命令的返回值,作为整个命令的返回值。也就是说,只要最后一个子命令不失败,管道命令总是会执行成功,因此它后面命令依然会执行,set -e就失效了。

请看下面这个例子。

#!/usr/bin/env bash
set -e

foo | echo a
echo bar

执行结果如下。

$ bash script.sh
a
script.sh:行4: foo: 未找到命令
bar

上面代码中,foo是一个不存在的命令,但是foo | echo a这个管道命令会执行成功,导致后面的echo bar会继续执行。
set -o pipefail用来解决这种情况,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。

#!/usr/bin/env bash
set -eo pipefail

foo | echo a
echo bar
运行后,结果如下。

$ bash script.sh
a
script.sh:行4: foo: 未找到命令

set -u

执行脚本的时候,如果遇到不存在的变量,Bash 默认忽略它。

#!/usr/bin/env bash

echo $a
echo bar
上面代码中,$a是一个不存在的变量。执行结果如下。

$ bash script.sh

bar
可以看到,echo $a输出了一个空行,Bash 忽略了不存在的$a,然后继续执行echo bar。大多数情况下,这不是开发者想要的行为,遇到变量不存在,脚本应该报错,而不是一声不响地往下执行。
set -u就用来改变这种行为。脚本在头部加上它,遇到不存在的变量就会报错,并停止执行。

#!/usr/bin/env bash
set -u

echo $a
echo bar

-u还有另一种写法-o nounset,两者是等价的。

set -o nounset

set -x

默认情况下,脚本执行后,屏幕只显示运行结果,没有其他内容。如果多个命令连续执行,它们的运行结果就会连续输出。有时会分不清,某一段内容是什么命令产生的。set -x用来在运行结果之前,先输出执行的那一行命令。

set -x 开始调试;

set +x 结束调试;

-x还有另一种写法-o xtrace。

set -o xtrace

 

grep 

退出码

退出状态如果找到选定的行,则退出状态为0,如果找不到,则为1。如果发生错误,则退出状态为2。(注:POSIX错误处理代码应检查'2'或更大。)

如果您想将退出代码强制设置为0,则只需将|| true附加到命令中即可:

echo 'Total' | grep -c No || true

 

-c参数

-c,--count禁止正常输出;而是为每个输入文件打印匹配行数。

因此,您所看到的是匹配项的计数,不要与grep匹配项的退出代码混淆

grep -c "xx" 文件  <=====> grep "xxx" 文件 | wc -l

 

grep范例

echo 'Total' | grep -c No
0
因此,“总计”中不存在“否”。但是然后查找其返回码,
echo $?
1

echo 'No' | grep -c No
1

echo $?
0

范例

范例1

#! /bin/bash

set -x

set -o pipefail

local mlnx_enabled=$(lspci -s "0000:5e:00.0" | grep -ci "Mellanox")

if [[ ${mlnx_enabled} -ne 0 ]]; then

      echo "Mellanox nic enabled"

else 

      echo "Mellanox nic not enable"

fi

 

对于在非Mellanox nic 的机器上,上诉代码会直接退出,而不会输出任何 echo 信息。

因为 grep -ci "Mellanox" 的 输出值为 0,退出码为 1;

由于退出码为1,即异常退出,此时 set -e 被设置,则会执行到 mlnx_enabled=$(lspci -s "0000:5e:00.0" | grep -ci "Mellanox")

时,程序直接退出;

范例2

#! /bin/bash

set -x

set -o pipefail

local mlnx_enabled=$(lspci -s "0000:5e:00.0" | grep -i "Mellanox" | wc -l)

if [[ ${mlnx_enabled} -ne 0 ]]; then

      echo "Mellanox nic enabled"

else 

      echo "Mellanox nic not enable"

fi

grep -i "Mellanox"  的退出码为1,但是  grep -i "Mellanox" | wc -l 整体的退出码其实是 wc -l 的退出码,不为1,所以不会由于 set -e  而退出。

但是由于 set -o pipefail 的存在,管道的任何一个子命令失败,都会退出。由于 grep -i "Mellanox" 失败,从而导致程序退出。

解决方法

失败cmd &&  或者 失败cmd || 就可以让 set -e 失效;

# cat test.sh
#!/usr/bin/env bash

set -e
set -o pipefail

echo "test1"
MELLANOX="FALSE"
lspci -s "0000:5e:00.0" | grep -ci "222Mellanox" && MELLANOX="true"
echo "$?"
echo "MELLANOX: ${MELLANOX}"

echo "test2"
MELLANOX="FALSE"
lspci -s "0000:5e:00.0" | grep -ci "Mellanox" && MELLANOX="true"
echo "$?"
echo "MELLANOX: ${MELLANOX}"

echo "test3"
MELLANOX="FALSE"
lspci -s "0000:5e:00.0" | grep -ci "2222Mellanox"
if [[ $? -eq 0 ]]; then
  MELLANOX="true"
fi
echo "MELLANOX: ${MELLANOX}"

 

# sh -x test.sh
+ set -e
+ set -o pipefail
+ echo test1
test1
+ MELLANOX=FALSE
+ lspci -s 0000:5e:00.0
+ grep -ci 222Mellanox
0
+ echo 1
1
+ echo 'MELLANOX: FALSE'
MELLANOX: FALSE
+ echo test2
test2
+ MELLANOX=FALSE
+ lspci -s 0000:5e:00.0
+ grep -ci Mellanox
1
+ MELLANOX=true
+ echo 0
0
+ echo 'MELLANOX: true'
MELLANOX: true
+ echo test3
test3
+ MELLANOX=FALSE
+ lspci -s 0000:5e:00.0
+ grep -ci 2222Mellanox
0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值