Bash 腳本 set 命令教程

Bash 腳本 set 命令教程
set命令是 Bash 腳本的重要環(huán)節(jié),卻常常被忽視,導(dǎo)致腳本的安全性和可維護(hù)性出問(wèn)題。本文介紹它的基本用法,讓你可以更安心地使用 Bash 腳本。

一、簡(jiǎn)介

我們知道,Bash 執(zhí)行腳本的時(shí)候,會(huì)創(chuàng)建一個(gè)新的 Shell。


$ bash script.sh

上面代碼中,script.sh是在一個(gè)新的 Shell 里面執(zhí)行。這個(gè) Shell 就是腳本的執(zhí)行環(huán)境,Bash 默認(rèn)給定了這個(gè)環(huán)境的各種參數(shù)。

set命令用來(lái)修改 Shell 環(huán)境的運(yùn)行參數(shù),也就是可以定制環(huán)境。一共有十幾個(gè)參數(shù)可以定制,官方手冊(cè)有完整清單,本文介紹其中最常用的四個(gè)。

順便提一下,如果命令行下不帶任何參數(shù),直接運(yùn)行set,會(huì)顯示所有的環(huán)境變量和 Shell 函數(shù)。


$ set

二、set -u

執(zhí)行腳本的時(shí)候,如果遇到不存在的變量,Bash 默認(rèn)忽略它。


#!/usr/bin/env bash

echo $a
echo bar

上面代碼中,$a是一個(gè)不存在的變量。執(zhí)行結(jié)果如下。


$ bash script.sh

bar

可以看到,echo $a輸出了一個(gè)空行,Bash 忽略了不存在的$a,然后繼續(xù)執(zhí)行echo bar。大多數(shù)情況下,這不是開(kāi)發(fā)者想要的行為,遇到變量不存在,腳本應(yīng)該報(bào)錯(cuò),而不是一聲不響地往下執(zhí)行。

set -u就用來(lái)改變這種行為。腳本在頭部加上它,遇到不存在的變量就會(huì)報(bào)錯(cuò),并停止執(zhí)行。


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

echo $a
echo bar

運(yùn)行結(jié)果如下。


$ bash script.sh
bash: script.sh:行4: a: 未綁定的變量

可以看到,腳本報(bào)錯(cuò)了,并且不再執(zhí)行后面的語(yǔ)句。

-u還有另一種寫(xiě)法-o nounset,兩者是等價(jià)的。


set -o nounset

三、set -x

默認(rèn)情況下,腳本執(zhí)行后,屏幕只顯示運(yùn)行結(jié)果,沒(méi)有其他內(nèi)容。如果多個(gè)命令連續(xù)執(zhí)行,它們的運(yùn)行結(jié)果就會(huì)連續(xù)輸出。有時(shí)會(huì)分不清,某一段內(nèi)容是什么命令產(chǎn)生的。

set -x用來(lái)在運(yùn)行結(jié)果之前,先輸出執(zhí)行的那一行命令。


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

echo bar

執(zhí)行上面的腳本,結(jié)果如下。


$ bash script.sh
+ echo bar
bar

可以看到,執(zhí)行echo bar之前,該命令會(huì)先打印出來(lái),行首以+表示。這對(duì)于調(diào)試復(fù)雜的腳本是很有用的。

-x還有另一種寫(xiě)法-o xtrace。


set -o xtrace

四、Bash 的錯(cuò)誤處理

如果腳本里面有運(yùn)行失敗的命令(返回值非0),Bash 默認(rèn)會(huì)繼續(xù)執(zhí)行后面的命令。


#!/usr/bin/env bash

foo
echo bar

上面腳本中,foo是一個(gè)不存在的命令,執(zhí)行時(shí)會(huì)報(bào)錯(cuò)。但是,Bash 會(huì)忽略這個(gè)錯(cuò)誤,繼續(xù)往下執(zhí)行。


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

可以看到,Bash 只是顯示有錯(cuò)誤,并沒(méi)有終止執(zhí)行。

這種行為很不利于腳本安全和除錯(cuò)。實(shí)際開(kāi)發(fā)中,如果某個(gè)命令失敗,往往需要腳本停止執(zhí)行,防止錯(cuò)誤累積。這時(shí),一般采用下面的寫(xiě)法。


command || exit 1

上面的寫(xiě)法表示只要command有非零返回值,腳本就會(huì)停止執(zhí)行。

如果停止執(zhí)行之前需要完成多個(gè)操作,就要采用下面三種寫(xiě)法。


# 寫(xiě)法一
command || { echo "command failed"; exit 1; }

# 寫(xiě)法二
if ! command; then echo "command failed"; exit 1; fi

# 寫(xiě)法三
command
if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi

另外,除了停止執(zhí)行,還有一種情況。如果兩個(gè)命令有繼承關(guān)系,只有第一個(gè)命令成功了,才能繼續(xù)執(zhí)行第二個(gè)命令,那么就要采用下面的寫(xiě)法。


command1 && command2

五、 set -e

上面這些寫(xiě)法多少有些麻煩,容易疏忽。set -e從根本上解決了這個(gè)問(wèn)題,它使得腳本只要發(fā)生錯(cuò)誤,就終止執(zhí)行。


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

foo
echo bar

執(zhí)行結(jié)果如下。


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

可以看到,第4行執(zhí)行失敗以后,腳本就終止執(zhí)行了。

set -e根據(jù)返回值來(lái)判斷,一個(gè)命令是否運(yùn)行失敗。但是,某些命令的非零返回值可能不表示失敗,或者開(kāi)發(fā)者希望在命令失敗的情況下,腳本繼續(xù)執(zhí)行下去。這時(shí)可以暫時(shí)關(guān)閉set -e,該命令執(zhí)行結(jié)束后,再重新打開(kāi)set -e。


set +e
command1
command2
set -e

上面代碼中,set +e表示關(guān)閉-e選項(xiàng),set -e表示重新打開(kāi)-e選項(xiàng)。

還有一種方法是使用command || true,使得該命令即使執(zhí)行失敗,腳本也不會(huì)終止執(zhí)行。


#!/bin/bash
set -e

foo || true
echo bar

上面代碼中,true使得這一行語(yǔ)句總是會(huì)執(zhí)行成功,后面的echo bar會(huì)執(zhí)行。

-e還有另一種寫(xiě)法-o errexit


set -o errexit

六、set -o pipefail

set -e有一個(gè)例外情況,就是不適用于管道命令。

所謂管道命令,就是多個(gè)子命令通過(guò)管道運(yùn)算符(|)組合成為一個(gè)大的命令。Bash 會(huì)把最后一個(gè)子命令的返回值,作為整個(gè)命令的返回值。也就是說(shuō),只要最后一個(gè)子命令不失敗,管道命令總是會(huì)執(zhí)行成功,因此它后面命令依然會(huì)執(zhí)行,set -e就失效了。

請(qǐng)看下面這個(gè)例子。


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

foo | echo a
echo bar

執(zhí)行結(jié)果如下。


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

上面代碼中,foo是一個(gè)不存在的命令,但是foo | echo a這個(gè)管道命令會(huì)執(zhí)行成功,導(dǎo)致后面的echo bar會(huì)繼續(xù)執(zhí)行。

set -o pipefail用來(lái)解決這種情況,只要一個(gè)子命令失敗,整個(gè)管道命令就失敗,腳本就會(huì)終止執(zhí)行。


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

foo | echo a
echo bar

運(yùn)行后,結(jié)果如下。


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

可以看到,echo bar沒(méi)有執(zhí)行。

七、總結(jié)

set命令的上面這四個(gè)參數(shù),一般都放在一起使用。


# 寫(xiě)法一
set -euxo pipefail

# 寫(xiě)法二
set -eux
set -o pipefail

這兩種寫(xiě)法建議放在所有 Bash 腳本的頭部。

另一種辦法是在執(zhí)行 Bash 腳本的時(shí)候,從命令行傳入這些參數(shù)。


$ bash -euxo pipefail script.sh

八、參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容