詳解Linux shell腳本的八個建議--值得收藏

腳本語言 Linux Bash Haskell Lua 收藏 AWK Sed Ubuntu 波波說運維 2019-05-21

概述

今天主要分享shell腳本的8條建議,僅做參考。


1. 指定bash

shell 腳本的第一行,#!之後應該是什麼?

這裡我見過/usr/bin/env bash,也見過/bin/bash,還有/usr/bin/bash,還有/bin/sh,還有/usr/bin/env sh。也算是常見的幾種寫法了。

詳解Linux shell腳本的八個建議--值得收藏

在多數情況下,以上五種寫法都是等價的。但如果系統的默認 shell 不是 bash 怎麼辦?比如某 Linux 發行版的某個版本,默認的 sh 就不是 bash。如果系統的 bash 不是在 /usr/bin/bash 怎麼辦?

這裡我推薦使用 /usr/bin/env bash 和 /bin/bash。前者通過env添加一箇中間層,讓env在$PATH中搜索bash;後者則是官方背書的,約定俗成的 bash 位置,/usr/bin/bash不過是指向它的一個符號鏈接。


2. set -e 和 set -x

在你開始構思並寫下具體的腳本邏輯之前,先插入一行set -e和一行set -x。

set -x會在執行每一行 shell 腳本時,把執行的內容輸出來。它可以讓你看到當前執行的情況,裡面涉及的變量也會被替換成實際的值。

set -e會在執行出錯時結束程序,就像其他語言中的“拋出異常”一樣。(準確說,不是所有出錯的時候都會結束程序,見下面的注)

注:set -e結束程序的條件比較複雜,在man bash裡面,足足用了一段話描述各種情景。大多數執行都會在出錯時退出,除非 shell 命令位於以下情況:

一個 pipeline 的非結尾部分,比如 error | ok
一個組合語句的非結尾部分,比如 ok && error || other
一連串語句的非結尾部分,比如 error; ok
位於判斷語句內,包括 test、if、 while 等等。

這兩個組合在一起用,可以在 debug 的時候替你節省許多時間,有必要在寫第一行具體的腳本之前就插入它們。


3.shellcheck

工欲善其事,必先利其器。這裡介紹一個 shell 腳本編寫神器:shellcheck

雖然寫了幾年 shell 腳本,但是從來不怎麼去記這些語法,這時候就要靠shellcheck來檢查了。

詳解Linux shell腳本的八個建議--值得收藏

shellcheck 除了可以提醒語法問題以外,還能檢查出 shell 腳本編寫常見的 bad code。

shellcheck採用haskell語言開發,在ubuntu中,可以直接採用apt install shellcheck安裝完成 。


4. 變量展開

在 shell 腳本中,偶爾可以看到這樣的做法:echo $xxx | awk/sed/grep/cut...。看起來大張形勢的樣子,其實不過是想修改一個變量的值。

詳解Linux shell腳本的八個建議--值得收藏

這裡bash 內建的變量展開機制已經足以滿足你各種需求!還是老方法, read the f**k manaul! man bash 然後搜索Parameter Expansion,下面就是你想要的技巧。


5. 注意local

當腳本內容比較多時,你開始把重複的邏輯提煉成函數。有可能會掉到 bash 的一個坑裡。在 bash,如果不加 local 限定詞,變量默認都是全局的。

變量默認全局——這跟 js 和 lua 相似;但相較而言,很少有 bash 教程一開始就告知你這個事實。在頂級作用域裡,是否是全局變量並不重要。但是在函數裡面,聲明一個全局變量可能會汙染到其他作用域(尤其在你根本沒有注意到這一點的情況下)。

所以,對於在函數內聲明的變量,請務必記得加上 local 限定詞。


6. trap 信號

像其他語言一樣,shell 也支持處理信號。trap sighandler INT可以在接收到 SIGINT 時調用 sighandler 函數。捕獲其他信號的方式以此類推。

不過 trap 的主要應用場景可不是捕獲哪個信號。trap 命令支持“捕獲”許多不同的流程——準確來說,允許用戶給特定的流程注入函數調用。其中最為常用的是trap func EXIT和trap func ERR。

trap func EXIT允許在腳本結束時調用函數。由於無論正常退出抑或異常退出,所註冊的函數都能得以調用,在需要調用一個清理函數的場景下,我都是用它註冊清理函數,而不是簡單地在腳本結尾調用清理函數。

trap func ERR允許在運行出錯時調用函數。一個常用的技法是,使用全局變量ERROR存儲錯誤信息,然後在註冊的函數中根據存儲的值完成對應的錯誤報告。把原本四分五裂的錯誤處理邏輯集中到一處,有時候會起奇效。不過要記住,程序異常退出時,既會調用EXIT註冊的函數,也會調用ERR註冊的函數。


7. 三思後行(比較虛)

這條建議的名字叫“三思而行”。其實無論寫什麼代碼,哪怕只是一個輔助腳本,都要三思而行,切忌粗心大意。不,寫腳本的時候更要記住這點。畢竟許多時候,一個複雜的腳本發端於幾行小小的命令。一開始寫這個腳本的人,也許以為它只是一次性任務。代碼裡難免對一些外部條件有些假定,在當時也許是正常的,但是隨著外部環境的變化,這些就成了隱藏的暗礁。另外一方面,平時也幾乎沒有人會給腳本做測試。除非你去運行它,否則不知道它是否還能正常使用。

要想減緩腳本代碼的腐爛速度,需要在編寫的時候辨清哪些是會變的依賴、哪些是腳本正常運行所不可或缺的。要有適當的抽象,編寫可變更的腳本。


8. 揚長避短(比較虛)

有些時候,使用 shell 寫腳本就意味著難以移植、難以統一地進行錯誤處理、難以利索地處理數據。

雖然使用外部的命令可以方便快捷地實現各種複雜的功能,但作為硬幣的反面,不得不依靠grep、sed、awk等各種工具把它們粘合在一起。

如果有兼容多平臺的需求,還得小心規避諸如BSD和GNU coreutils,bash版本差異之類奇奇怪怪的陷阱。

由於缺乏完善的數據結構以及一致的API,shell 腳本在處理複雜的邏輯上力不從心。

解決特定的問題要用合適的工具。知道什麼時候用 shell,什麼時候切換到另外一門更通用的腳本語言(比如ruby/python/perl),這也是編寫可靠 shell 腳本的訣竅。如果你的任務可以組合常見的命令來完成,而且只涉及簡單的數據,那麼 shell 腳本就是適合的錘子。如果你的任務包含較為複雜的邏輯,而且數據結構複雜,那麼你需要用 ruby/python 之類的語言編寫腳本。


今天內容就分享到這了,後面會分享更多devops和DBA方面的內容,感興趣的朋友可以關注一下~

詳解Linux shell腳本的八個建議--值得收藏

相關推薦

推薦中...