shell 中的 set -e 導致的退出問題
目錄
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
相關文章
- shell中set指令的用法
- ANALYZE導致的阻塞問題分析
- Linux終端退出後導致nohup程式退出Linux
- 在https中引入http資源所導致的問題HTTP
- 分散式鎖導致的超賣問題分散式
- MySQL8.0 view導致的效能問題MySqlView
- CAS導致的ABA問題及解決
- golang slice使用不慎導致的問題Golang
- 記一次crontab中date命令錯用導致的問題
- str_replace導致的注入問題彙總
- [20191204]sqlplus特殊定義導致的問題.txtSQL
- 關於 Laravel mix 導致 Bootstrap 失效的問題Laravelboot
- ARC下的block導致的迴圈引用問題解析BloC
- set -e
- 【爬坑】.Net編譯環境導致的問題編譯
- oracle 序列值導致的主鍵衝突問題Oracle
- vue的scope導致樣式修改不了問題Vue
- EfCore3的OwnedType會導致Sql效率問題SQL
- Makefile與Shell的問題
- netcore服務程式暴力退出導致的業務資料不一致的一種解決方案(優雅退出)NetCore
- c++臨時物件導致的生命週期問題C++物件
- 網路問題導致更多的資料中心中斷
- 線上問題排查:記一次 Redis Cluster Pipeline 導致的死鎖問題Redis
- 記錄一次fs配置導致串線的問題
- WPF App後臺檔案彈窗導致奇怪的問題APP
- 記一次儲存問題導致的rac故障案例
- MySQL:一次timestamp時區轉換導致的問題MySql
- 關於 iconv 轉碼導致資料丟失的問題
- 記一次 Mac 意外重啟導致的 Homestead 問題Mac
- xxl-job濫用netty導致的問題和解決方案Netty
- 使用資料庫處理併發可能導致的問題資料庫
- Atlas VPN:2021年全球76%的公司遭遇因技術問題導致的業務中斷
- 解決 allure.dynamic.parameter 不生效問題 或者 allure 報告中因為引數導致排版的問題
- Vmware相容問題導致電腦藍屏
- 有問題的mybatis的sql導致對資料庫進行了批量的修改MyBatisSQL資料庫
- [分享]關於新版本 Composer 會導致 Class not found 的問題
- Oracle資料傾斜導致的問題-無繫結變數Oracle變數
- Oracle資料傾斜導致的問題-有繫結變數Oracle變數