您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關如何寫好shell腳本的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
前言
產品的最終用戶通常不懂技術,所以不管你怎么折騰產品代碼都無所謂。但腳本代碼不一樣,它們是開發人員寫給開發人員的。
有多少次,你運行./script.sh,然后輸出一些東西,但卻不知道它剛剛都做了些什么。這是一種很糟糕的腳本用戶體驗。我將在這篇文章中介紹如何寫出具有良好開發者體驗的 shell 腳本。
產品的最終用戶通常不懂技術,所以不管你怎么折騰產品代碼都無所謂。但腳本代碼不一樣,它們是開發人員寫給開發人員的。
這樣會導致一些問題:
混亂的腳本——我知道,我們都是工程師,讀得懂代碼,但即使這樣,也請為我們這些對 Shell 腳本不是很熟練的人考慮一下(我們在寫代碼時也會為你們考慮的)。
滿屏的日志和錯誤輸出——就算我們也是工程師,并不代表我們了解你所做的一切。
弄得一團糟卻沒有做好清理工作——是的,我們可以順著你的腳本手動撤銷變更,但你真的會讓那些信任你的腳本的人這么做嗎?
所以,我們可以通過一些方法來為自己和別人寫出更好的 shell 腳本。這里給出的所有示例都可以使用與 POSIX 標準兼容的 shell 運行(#!/bin/sh),因為它是最常用的。嫌文章太長了可以只看以下總結部分:
提供--help標記
檢查所有命令的可用性
獨立于當前工作目錄
如何讀取輸入:環境變量 vs. 標記
打印對系統執行的所有操作
如果有必要,提供--silent選項
重新開啟顯示
用動畫的方式顯示進度
用顏色編碼輸出
出現錯誤立即退出腳本
自己執行清理工作
在退出時使用不同的錯誤碼
在結束時打印一個新行
有時間的話可以接著往下看具體內容:
提供--help標記
安裝在系統上的二進制文件通常帶有man幫助文檔,但對于腳本來說就不一定了。因此我們通常需要為腳本提供-h或--help標記來打印有關如何使用腳本的信息。如果其他工程師需要修改腳本,這也可以作為腳本的內聯文檔:
#!/bin/sh if [ ${#@} -ne 0 ] && [ "${@#"--help"}" = "" ]; then printf -- '...help...\n'; exit 0; fi;
這段腳本先計算參數長度(${#@} -ne 0),只有當參數長度不為零時才會檢查--help標記。下一個條件會檢查參數中是否存在字符串“--help” 。第一個條件是必需的,如果參數長度為零則不需要打印幫助信息。
檢查所有命令的可用性
腳本通常會調用其他腳本或二進制文件。在調用可能不存在的命令時,請先檢查它們是否可用。可以使用“command -v 二進制文件名稱”來執行此操作,看看它的退出代碼是否為零。如果命令不可用,可以告訴用戶應該如何獲得這個二進制文件:
#!/bin/sh _=$(command -v docker); if [ "$?" != "0" ]; then printf -- 'You don\'t seem to have Docker installed.\n'; printf -- 'Get it: https://www.docker.com/community-edition\n'; printf -- 'Exiting with code 127...\n'; exit 127; fi; # ...
獨立于當前工作目錄
從不同的目錄執行腳本可能會發生錯誤,這樣的腳本沒有人會喜歡。要解決這個問題,請使用絕對路徑(/path/to/something)和腳本的相對路徑(如下所示)。
可以使用dirname $0引用腳本的當前路徑:
#!/bin/sh CURR_DIR="$(dirname $0);" printf -- 'moving application to /opt/app.jar'; mv "${CURR_DIR}/application.jar" /opt/app.jar;
如何讀取輸入:環境變量 vs. 標記
腳本通過兩種方式接受輸入:環境變量和選項標記(參數)。根據經驗,對于不影響腳本行為的值,可以使用環境變量,而對于可能觸發腳本不同流程的值,可以使用腳本參數。
不影響腳本行為的變量可能是訪問令牌和 ID 之類的東西:
#!/bin/sh # do this export AWS_ACCESS_TOKEN='xxxxxxxxxxxx'; ./provision-everything # and not ./provisiong-everything --token 'xxxxxxxxxxx';
影響腳本行為的變量可能是需要運行實例的數量、是異步還是同步運行、是否在后臺運行等參數:
#!/bin/sh # do this ./provision-everything --async --instance-count 400 # and not INSTANCE_COUNT=400 ASYNC=true ./provision-everything
打印對系統執行的所有操作
腳本通常會對系統執行有狀態的更改。不過,由于我們不知道用戶何時會向發送SIGINT,也不知道腳本錯誤何時可能導致腳本意外終止,因此很有必要將正在做的事情打印在終端上,這樣用戶就可以在不去查看腳本的情況下回溯這些步驟:
#!/bin/sh printf -- 'Downloading required document to ./downloaded... '; wget -o ./downloaded https://some.site.com/downloaded; printf -- 'Moving ./downloaded to /opt/downloaded...'; mv ./downloaded /opt/; printf -- 'Creating symlink to /opt/downloaded...'; ln -s /opt/downloaded /usr/bin/downloaded;
在必要時提供--silent選項
有些腳本是為了將其輸出傳給其他腳本。雖說腳本都應該能夠單獨運行,不過有時候也有必要讓它們把輸出結果傳給另一個腳本。可以利用stty -echo來實現--silent標記:
#!/bin/sh if [ ${#@} -ne 0 ] && [ "${@#"--silent"}" = "" ]; then stty -echo; fi; # ... # before point of intended output: stty +echo && printf -- 'intended output\n'; # silence it again till end of script stty -echo; # ... stty +echo; exit 0;
重新開啟顯示
在使用stty -echo關閉腳本顯示之后,如果發生致命錯誤,腳本將終止,而且不會恢復終端輸出,這樣對用戶來說是沒有意義的。可以使用trap來捕捉SIGINT和其他操作系統級別的信號,然后使用stty echo打開終端顯示:
#!/bin/sh error_handle() { stty echo; } if [ ${#@} -ne 0 ] && [ "${@#"--silent"}" = "" ]; then stty -echo; trap error_handle INT; trap error_handle TERM; trap error_handle KILL; trap error_handle EXIT; fi; # ...
用動畫的方式顯示進度
有些命令需要運行很長時間,并非所有腳本都提供了進度條。在用戶等待異步任務完成時,可以通過一些方式告訴他們腳本仍在運行。比如在while循環中打印一些信息:
#!/bin/sh printf -- 'Performing asynchronous action..'; ./trigger-action; DONE=0; while [ $DONE -eq 0 ]; do ./async-checker; if [ "$?" = "0" ]; then DONE=1; fi; printf -- '.'; sleep 1; done; printf -- ' DONE!\n';
或者可以做一些更好玩的小玩意兒,比如 http://mywiki.wooledge.org/BashFAQ/034。
用顏色編碼輸出
在腳本中調用其他二進制文件或腳本時,對它們的輸出進行顏色編碼,這樣就可以知道哪個輸出來自哪個腳本或二進制文件。這樣我們就不需要在滿屏的黑白輸出文本中查找想要的輸出結果。
理想情況下,腳本應該輸出白色(默認的,前臺進程),子進程應該使用灰色(通常不需要,除非出現錯誤),使用綠色表示成功,紅色表示失敗,黃色表示警告。
#!/bin/sh printf -- 'doing something... \n'; printf -- '\033[37m someone else's output \033[0m\n'; printf -- '\033[32m SUCCESS: yay \033[0m\n'; printf -- '\033[33m WARNING: hmm \033[0m\n'; printf -- '\033[31m ERROR: fubar \033[0m\n';
可以使用\033[Xm,其中X代表顏色代碼。有些腳本使用\e而不是\033,但要注意\e不適用于所有的 UNIX 系統。
正確示范
可在.sh 中使用的所有顏色和修飾符 https://misc.flogisoft.com/bash/tip_colors_and_formatting。
出現錯誤立即退出腳本
set -e表示從當前位置開始,如果出現任何錯誤都將觸發EXIT。相反,set +e表示不管出現任何錯誤繼續執行腳本。
如果腳本是有狀態的(每個后續步驟都依賴前一個步驟),那么請使用set -e,在腳本出現錯誤時立即退出腳本。如果要求所有命令都要執行完(很少會這樣),那么就使用set +e。
#!/bin/sh set +e; ./script-1; ./script-2; # does not depend on ./script-1 ./script-3; # does not depend on ./script-2 set -e; ./script-4; ./script-5; # depends on success of ./script-4 # ...
自己執行清理工作
大多數腳本在出現錯誤時不會執行清理工作,能夠做好這方面工作的腳本實屬罕見,但這樣做其實很有用,還可以省下不少時間。前面已經給出過示例,讓stty恢復正常,并借助trap命令來執行清理工作:
#!/bin/sh handle_exit_code() { ERROR_CODE="$?"; printf -- "an error occurred. cleaning up now... "; # ... cleanup code ... printf -- "DONE.\nExiting with error code ${ERROR_CODE}.\n"; exit ${ERROR_CODE}; } trap "handle_exit_code" EXIT; # ... actual script...
在退出時使用不同的錯誤碼
在絕大多數 shell 腳本中,exit 0 表示執行成功,exit 1 表示發生錯誤。對錯誤與錯誤碼進行一對一的映射,這樣有助于腳本調試。
#!/bin/sh # ... if [ "$?" != "0" ]; then printf -- 'X happened. Exiting with status code 1.\n'; exit 1; fi; # ... if [ "$?" != "0" ]; then printf -- 'Y happened. Exiting with status code 2.\n'; exit 2; fi;
這樣做有另一個額外的好處,就是其他腳本在調用你的腳本時,可以根據錯誤碼來判斷發生了什么錯誤。
在結束時打印一個新行
如果你有在遵循腳本的最佳實踐,那么可能會使用printf代替echo(它在不同系統中的行為有所差別)。問題是printf在命令結束后不會自動添加一個新行,導致控制臺看起來是這樣的:
看起來是多么的平淡
這樣一點也不酷,可以通過簡單的方式打印一個新行:
#!/bin/sh # ... your awesome script ... printf -- '\n'; exit 0;
這樣就可以得到:
感謝各位的閱讀!關于“如何寫好shell腳本”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。