2019年10月7日 星期一

[Linux 文章收集] Bash 腳本set 命令教程

Source From Here
一、簡介
set 命令是 Bash 腳本的重要環節,卻常常被忽視,導致腳本的安全性和可維護性出問題。本文介紹它的基本用法,讓你可以更安心地使用 Bash 腳本。我們知道,Bash 執行腳本的時候,會創建一個新的 Shell:
$ bash script.sh

上面代碼中,script.sh 是在一個新的 Shell 裡面執行。這個 Shell 就是腳本的執行環境,Bash 默認給定了這個環境的各種參數。set 命令用來修改 Shell 環境的運行參數,也就是可以定制環境。一共有十幾個參數可以定制,官方手冊有完整清單,本文介紹其中最常用的四個。

二、set -u
執行腳本的時候,如果遇到不存在的變量,Bash 默認忽略它:
  1. #!/usr/bin/env bash  
  2.   
  3. echo $a  
  4. echo bar  
上面代碼中,$a 是一個不存在的變量。執行結果如下:
$ bash script.sh

bar

可以看到,echo $a 輸出了一個空行,Bash 忽略了不存在的 $a,然後繼續執行 echo bar。大多數情況下,這不是開發者想要的行為,遇到變量不存在,腳本應該報錯,而不是一聲不響地往下執行。 set -u 就用來改變這種行為。腳本在頭部加上它,遇到不存在的變量就會報錯,並停止執行。
  1. #!/usr/bin/env bash  
  2. set -u  
  3.   
  4. echo $a  
  5. echo bar  
運行結果如下:
$ bash script.sh
bash: script.sh:行4: a: 未绑定的变量

可以看到,腳本報錯了,並且不再執行後面的語句。-u 還有另一種寫法 -o nounset,兩者是等價的:
三、set -x
默認情況下,腳本執行後,屏幕只顯示運行結果,沒有其他內容。如果多個命令連續執行,它們的運行結果就會連續輸出。有時會分不清,某一段內容是什麼命令產生的。set -x 用來在運行結果之前,先輸出執行的那一行命令:
  1. #!/usr/bin/env bash  
  2. set -x  
  3.   
  4. echo bar  
執行上面的腳本,結果如下:
$ bash script.sh
+ echo bar
bar

可以看到,執行 echo bar 之前,該命令會先打印出來,行首以 + 表示。這對於調試複雜的腳本是很有用的。-x 還有另一種寫法 -o xtrace:
四、Bash 的錯誤處理
如果腳本里面有運行失敗的命令(返回值非 0),Bash 默認會繼續執行後面的命令:
  1. #!/usr/bin/env bash  
  2.   
  3. foo  
  4. echo bar  
面腳本中,foo 是一個不存在的命令,執行時會報錯。但是,Bash 會忽略這個錯誤,繼續往下執行:
$ bash script.sh
script.sh:行3: foo: 未找到命令
bar

可以看到,Bash 只是顯示有錯誤,並沒有終止執行。這種行為很不利於腳本安全和除錯。實際開發中,如果某個命令失敗,往往需要腳本停止執行,防止錯誤累積。這時,一般採用下面的寫法:
  1. command || exit 1  
上面的寫法表示只要 command 有非零返回值,腳本就會停止執行。如果停止執行之前需要完成多個操作,就要採用下面三種寫法:
  1. # 写法一  
  2. command || { echo "command failed"; exit 1; }  
  3.   
  4. # 写法二  
  5. if ! command; then echo "command failed"; exit 1; fi  
  6.   
  7. # 写法三  
  8. command  
  9. if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi  
另外,除了停止執行,還有一種情況。如果兩個命令有繼承關係,只有第一個命令成功了,才能繼續執行第二個命令,那麼就要採用下面的寫法:
  1. command1 && command2  
五、 set -e
上面這些寫法多少有些麻煩,容易疏忽。set -e 從根本上解決了這個問題,它使得腳本只要發生錯誤,就終止執行:
  1. #!/usr/bin/env bash  
  2. set -e  
  3.   
  4. foo  
  5. echo bar  
執行結果如下。
$ bash script.sh
script.sh:行4: foo: 未找到命令

可以看到,第4行執行失敗以後,腳本就終止執行了。

set -e 根據返回值來判斷,一個命令是否運行失敗。但是,某些命令的非零返回值可能不表示失敗,或者開發者希望在命令失敗的情況下,腳本繼續執行下去。這時可以暫時關閉 set -e,該命令執行結束後,再重新打開 set -e:
  1. set +e  
  2. command1  
  3. command2  
  4. set -e  
上面代碼中,set +e 表示關閉 -e 選項set -e 表示重新打開 -e 選項。還有一種方法是使用 command || true,使得該命令即使執行失敗,腳本也不會終止執行:
  1. #!/bin/bash  
  2. set -e  
  3.   
  4. foo || true  
  5. echo bar  
上面代碼中,true 使得這一行語句總是會執行成功,後面的 echo bar 會執行。-e 還有另一種寫法 -o errexit
六、set -o pipefail
set -e 有一個例外情況,就是不適用於管道命令。所謂管道命令,就是多個子命令通過管道運算符(|)組合成為一個大的命令。Bash 會把最後一個子命令的返回值,作為整個命令的返回值。也就是說,只要最後一個子命令不失敗,管道命令總是會執行成功,因此它後面命令依然會執行,set -e 就失效了。

請看下面這個例子:
  1. #!/usr/bin/env bash  
  2. set -e  
  3.   
  4. foo | echo a  
  5. echo bar  
執行結果如下:
$ bash script.sh
a
script.sh:行4: foo: 未找到命令
bar

上面代碼中,foo 是一個不存在的命令,但是 foo | echo a 這個管道命令會執行成功,導致後面的echo bar會繼續執行。set -o pipefail 用來解決這種情況,只要一個子命令失敗,整個管道命令就失敗,腳本就會終止執行:
  1. #!/usr/bin/env bash  
  2. set -eo pipefail  
  3.   
  4. foo | echo a  
  5. echo bar  
運行後,結果如下。
$ bash script.sh
a
script.sh:行4: foo: 未找到命令

可以看到,echo bar 沒有執行。

七、總結
set 命令的上面這四個參數,一般都放在一起使用。
  1. # 写法一  
  2. set -euxo pipefail  
  3.   
  4. # 写法二  
  5. set -eux  
  6. set -o pipefail  
這兩種寫法建議放在所有Bash 腳本的頭部。另一種辦法是在執行Bash 腳本的時候,從命令行傳入這些參數:
$ bash -euxo pipefail script.sh


沒有留言:

張貼留言

[Git 文章收集] Differences between git merge and git rebase

Source From  Here Preface Merging and rebasing are the two most popular way to applying changes from one branch into another one. They bot...