我們知道,Bash 執行腳本的時候,會建立一個新的 Shell。
$ bash script.sh
上面代碼中,script.sh是在一個新的 Shell 裡面執行。這個 Shell 就是腳本的執行環境,Bash 預設給定了這個環境的各種參數。
set指令用來修改 Shell 環境的運作參數,也就是可以定制環境。一共有十幾個參數可以定制,官方手冊有完整清單,本文介紹其中最常用的四個。
1)set指令行下不帶任何參數,直接運作set:
會顯示所有的環境變量和 Shell 函數
$ set
BASH=/bin/bash
BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:histappend:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath
BASH_ALIASES=()
BASH_ARGC=()
BASH_ARGV=()
BASH_CMDS=()
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu")
BASH_VERSION='4.2.46(1)-release'
COLUMNS=210
DIRSTACK=()
DT=20191025_164714
...
2)set -u:
執行腳本的時候,如果遇到不存在的變量,Bash 預設忽略它。例如:
#腳本script.sh:
#!/usr/bin/env bash
echo $a
echo bar
#執行:
$ sh script.sh
bar
可以看到$a雖然不存在,但沒有報錯。大多數情況下,這不是開發者想要的行為,遇到變量不存在,腳本應該報錯,而不是一聲不響地往下執行。set -u就用來改變這種行為。腳本在頭部加上它,遇到不存在的變量就會報錯,并停止執行。
#!/usr/bin/env bash
set -u
echo $a
echo bar
$ sh script.sh
bash: script.sh:行4: a: 未綁定的變量
3)set -x:
預設情況下,腳本執行後,螢幕隻顯示運作結果,沒有其他内容。如果多個指令連續執行,它們的運作結果就會連續輸出。有時會分不清,某一段内容是什麼指令産生的。set -x用來在運作結果之前,先輸出執行的那一行指令。
#腳本 script.sh
#!/usr/bin/env bash
set -x
echo bar
#執行
$ sh script.sh
+ echo bar
bar
可以看到,執行
echo bar
之前,該指令會先列印出來,行首以
+
表示。這對于調試複雜的腳本是很有用的。
4)set -e:
如果腳本裡面有運作失敗的指令(傳回值非0),Bash 預設會繼續執行後面的指令。例如:
#腳本 script.sh
#!/usr/bin/env bash
foo
echo bar
#執行
$ sh script.sh
script.sh:行3: foo: 未找到指令
bar
可以看到,Bash 隻是顯示有錯誤,并沒有終止執行,這種行為很不利于腳本安全和除錯。set -e從根本上解決了這個問題,它使得腳本隻要發生錯誤,就終止執行。例如:
#腳本 script.sh
#!/usr/bin/env bash
set -e
foo
echo bar
#執行
$ sh script.sh
script.sh:行4: foo: 未找到指令
可以看到,第4行執行失敗以後,腳本就終止執行了。
set -e根據傳回值來判斷,一個指令是否運作失敗。但是,某些指令的非零傳回值可能不表示失敗,或者開發者希望在指令失敗的情況下,腳本繼續執行下去。這時可以暫時關閉set -e,該指令執行結束後,再重新打開set -e。
set +e
command1
command2
set -e
5)set -o pipefail:
set -e有一個例外情況,就是不适用于管道指令。所謂管道指令,就是多個子指令通過管道運算符(|)組合成為一個大的指令。Bash 會把最後一個子指令的傳回值,作為整個指令的傳回值。也就是說,隻要最後一個子指令不失敗,管道指令總是會執行成功,是以它後面指令依然會執行,set -e就失效了。例如:
#腳本 script.sh
#!/usr/bin/env bash
set -e
foo | echo a
echo bar
#執行
$ sh script.sh
$ bash script.sh
a
script.sh:行4: foo: 未找到指令
bar
上面代碼中,
foo
是一個不存在的指令,但是
foo | echo a
這個管道指令會執行成功,導緻後面的
echo bar
會繼續執行。
#腳本 script.sh
#!/usr/bin/env bash
set -eo pipefail
foo | echo a
echo bar
#執行
$ sh script.sh
$ bash script.sh
a
script.sh:行4: foo: 未找到指令