溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Shell腳本編寫(xiě)的可靠建議有哪些

發(fā)布時(shí)間:2021-09-16 14:50:32 來(lái)源:億速云 閱讀:135 作者:柒染 欄目:開(kāi)發(fā)技術(shù)

今天就跟大家聊聊有關(guān)Shell腳本編寫(xiě)的可靠建議有哪些,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

1. 指定bash

shell 腳本的第一行,#!之后應(yīng)該是什么?如果拿這個(gè)問(wèn)題去問(wèn)別人,不同的人的回答可能各不相同。

我見(jiàn)過(guò)/usr/bin/env bash,也見(jiàn)過(guò)/bin/bash,還有/usr/bin/bash,還有/bin/sh,還有/usr/bin/env sh。這算是編程界的“'茴'字四種寫(xiě)法”了。

在多數(shù)情況下,以上五種寫(xiě)法都是等價(jià)的。但是,寫(xiě)過(guò)程序的人都知道:“少數(shù)情況”里往往隱藏著意想不到的坑。

如果系統(tǒng)的默認(rèn) shell 不是 bash 怎么辦?比如某 Linux 發(fā)行版的某個(gè)版本,默認(rèn)的 sh 就不是 bash。

如果系統(tǒng)的 bash 不是在 /usr/bin/bash 怎么辦?

我推薦使用 /usr/bin/env bash 和 /bin/bash。前者通過(guò)env添加一個(gè)中間層,讓env在$PATH中搜索bash;后者則是官方背書(shū)的,約定俗成的 bash 位置,/usr/bin/bash不過(guò)是指向它的一個(gè)符號(hào)鏈接。

2. set -e 和 set -x

OK,經(jīng)過(guò)一番討論,現(xiàn)在第一行定下來(lái)了。接下來(lái)該開(kāi)始寫(xiě)第二行了吧?

且慢!在你開(kāi)始構(gòu)思并寫(xiě)下具體的代碼邏輯之前,先插入一行set -e和一行set -x。

set -x會(huì)在執(zhí)行每一行 shell 腳本時(shí),把執(zhí)行的內(nèi)容輸出來(lái)。它可以讓你看到當(dāng)前執(zhí)行的情況,里面涉及的變量也會(huì)被替換成實(shí)際的值。

set -e會(huì)在執(zhí)行出錯(cuò)時(shí)結(jié)束程序,就像其他語(yǔ)言中的“拋出異?!币粯?。(準(zhǔn)確說(shuō),不是所有出錯(cuò)的時(shí)候都會(huì)結(jié)束程序,見(jiàn)下面的注)

注:set -e結(jié)束程序的條件比較復(fù)雜,在man bash里面,足足用了一段話(huà)描述各種情景。大多數(shù)執(zhí)行都會(huì)在出錯(cuò)時(shí)退出,除非 shell 命令位于以下情況:

一個(gè) pipeline 的非結(jié)尾部分,比如 error | ok

一個(gè)組合語(yǔ)句的非結(jié)尾部分,比如 ok && error || other

一連串語(yǔ)句的非結(jié)尾部分,比如 error; ok

位于判斷語(yǔ)句內(nèi),包括test、if、while等等。
這兩個(gè)組合在一起用,可以在 debug 的時(shí)候替你節(jié)省許多時(shí)間。出于防御性編程的考慮,有必要在寫(xiě)第一行具體的代碼之前就插入它們。捫心自問(wèn),寫(xiě)代碼的時(shí)候能夠一次寫(xiě)對(duì)的次數(shù)有多少?大多數(shù)代 碼,在提交之前,通常都經(jīng)歷過(guò)反復(fù)調(diào)試修改的過(guò)程。與其在焦頭爛額之際才引入這兩個(gè)配置,不如一開(kāi)始就給 debug 留下余地。在代碼終于可以提交之后,再考慮是否保留它們也不遲。

3. 帶上shellcheck

好了,現(xiàn)在我已經(jīng)有了三行(樣板)代碼,具體的業(yè)務(wù)邏輯一行都沒(méi)寫(xiě)呢。是不是該開(kāi)始寫(xiě)了?

且慢!工欲善其事,必先利其器。這次,我就介紹一個(gè) shell 腳本編寫(xiě)神器:shellcheck

說(shuō)來(lái)慚愧,雖然寫(xiě)了幾年 shell 腳本,有些語(yǔ)法我還是記不清楚。這時(shí)候就要依仗 shellcheck 指點(diǎn)一下了。shellcheck 除了可以提醒語(yǔ)法問(wèn)題以外,還能檢查出 shell 腳本編寫(xiě)常見(jiàn)的 bad code。本來(lái)我的N條建議里面,還有幾條是關(guān)于這些 bad code 的,不過(guò)考慮到 shellcheck 完全可以發(fā)掘出這些問(wèn)題,于是忍痛把它們都剔除在外了。毫無(wú)疑問(wèn),使用 shellcheck 給我的 shell 編寫(xiě)技能帶來(lái)了巨大的飛躍。

所謂“站在巨人的肩膀上”,雖然我們這些新兵蛋子,技能不如老兵們強(qiáng),但是我們可以在裝備上趕上對(duì)方?。?dòng)動(dòng)手安裝一下,就能結(jié)識(shí)一個(gè)循循善誘的“老師”,何樂(lè)而不為?
順便一提,shellcheck 居然是用 haskell 寫(xiě)的。誰(shuí)說(shuō) haskell 只能用來(lái)裝逼?

4. 變量展開(kāi)

在 shell 腳本中,偶爾可以看到這樣的做法:echo $xxx | awk/sed/grep/cut... ??雌饋?lái)大張形勢(shì)的樣子,其實(shí)不過(guò)是想修改一個(gè)變量的值。殺雞何必用牛刀?bash內(nèi)建的變量展開(kāi)機(jī)制已經(jīng)足以滿(mǎn)足你各種需求!還是老方法, read the f**k manaul! man bash 然后搜索Parameter Expansion,下面就是你想要的技巧。

5. 注意local

隨著代碼越寫(xiě)越多,你開(kāi)始把重復(fù)的邏輯提煉成函數(shù)。有可能你會(huì)掉到bash的一個(gè)坑里。在bash,如果不加 local 限定詞,變量默認(rèn)都是全局的。變量默認(rèn)全局——這跟 js 和 lua 相似;但相較而言,很少有 bash 教程一開(kāi)始就告知你這個(gè)事實(shí)。在頂級(jí)作用域里,是否是全局變量并不重要。但是在函數(shù)里面,聲明一個(gè)全局變量可能會(huì)污染到其他作用域(尤其在你根本沒(méi)有注意 到這一點(diǎn)的情況下)。所以,對(duì)于在函數(shù)內(nèi)聲明的變量,請(qǐng)務(wù)必記得加上 local 限定詞。

6. trap信號(hào)

如果你寫(xiě)過(guò)稍微復(fù)雜點(diǎn)的在后臺(tái)運(yùn)行的程序,應(yīng)該知道 posix 標(biāo)準(zhǔn)里面“信號(hào)”是什么一回事。如果不知道,直接看下一段。像其他語(yǔ)言一樣,shell 也支持處理信號(hào)。trap sighandler INT可以在接收到 SIGINT 時(shí)調(diào)用 sighandler 函數(shù)。捕獲其他信號(hào)的方式以此類(lèi)推。

不過(guò) trap 的主要應(yīng)用場(chǎng)景可不是捕獲哪個(gè)信號(hào)。trap 命令支持“捕獲”許多不同的流程——準(zhǔn)確來(lái)說(shuō),允許用戶(hù)給特定的流程注入函數(shù)調(diào)用。其中最為常用的是trap func EXIT和trap func ERR。

trap func EXIT允許在腳本結(jié)束時(shí)調(diào)用函數(shù)。由于無(wú)論正常退出抑或異常退出,所注冊(cè)的函數(shù)都能得以調(diào)用,在需要調(diào)用一個(gè)清理函數(shù)的場(chǎng)景下,我都是用它注冊(cè)清理函數(shù),而不是簡(jiǎn)單地在腳本結(jié)尾調(diào)用清理函數(shù)。

trap func ERR允許在運(yùn)行出錯(cuò)時(shí)調(diào)用函數(shù)。一個(gè)常用的技法是,使用全局變量ERROR存儲(chǔ)錯(cuò)誤信息,然后在 注冊(cè)的函數(shù)中根據(jù)存儲(chǔ)的值完成對(duì)應(yīng)的錯(cuò)誤報(bào)告。把原本四分五裂的錯(cuò)誤處理邏輯集中到一處,有時(shí)候會(huì)起奇效。不過(guò)要記住,程序異常退出時(shí),既會(huì)調(diào)用EXIT 注冊(cè)的函數(shù),也會(huì)調(diào)用ERR注冊(cè)的函數(shù)。

7. 三思后行

以上幾條都是具體的建議,剩下兩條比較務(wù)虛。

這條建議的名字叫“三思而行”。其實(shí)無(wú)論寫(xiě)什么代碼,哪怕只是一個(gè)輔助腳本,都要三思而行,切忌粗心大意。不,寫(xiě)腳本的時(shí)候更要記住這點(diǎn)。畢竟許多 時(shí)候,一個(gè)復(fù)雜的腳本發(fā)端于幾行小小的命令。一開(kāi)始寫(xiě)這個(gè)腳本的人,也許以為它只是一次性任務(wù)。代碼里難免對(duì)一些外部條件有些假定,在當(dāng)時(shí)也許是正常的, 但是隨著外部環(huán)境的變化,這些就成了隱藏的暗礁。雪上加霜的是,幾乎沒(méi)有人會(huì)給腳本做測(cè)試。除非你去運(yùn)行它,否則不知道它是否還能正常使用。

要想減緩腳本代碼的腐爛速度,需要在編寫(xiě)的時(shí)候辨清哪些是會(huì)變的依賴(lài)、哪些是腳本正常運(yùn)行所不可或缺的。要有適當(dāng)?shù)某橄?,編?xiě)可變更的代碼;同時(shí)要有防御性編程的意識(shí),給自己的代碼一道護(hù)城河。

8. 揚(yáng)長(zhǎng)避短

有些時(shí)候,使用 shell 寫(xiě)腳本就意味著難以移植、難以統(tǒng)一地進(jìn)行錯(cuò)誤處理、難以利索地處理數(shù)據(jù)。
雖然使用外部的命令可以方便快捷地實(shí)現(xiàn)各種復(fù)雜的功能,但作為硬幣的反面,不得不依靠grep、sed、awk等各種工具把它們粘合在一起。

如果有兼容多平臺(tái)的需求,還得小心規(guī)避諸如BSD和GNU coreutils,bash版本差異之類(lèi)奇奇怪怪的陷阱。

由于缺乏完善的數(shù)據(jù)結(jié)構(gòu)以及一致的API,shell 腳本在處理復(fù)雜的邏輯上力不從心。

解決特定的問(wèn)題要用合適的工具。知道什么時(shí)候用 shell,什么時(shí)候切換到另外一門(mén)更通用的腳本語(yǔ)言(比如ruby/python/perl),這也是編寫(xiě)可靠 shell 腳本的訣竅。如果你的任務(wù)可以組合常見(jiàn)的命令來(lái)完成,而且只涉及簡(jiǎn)單的數(shù)據(jù),那么 shell 腳本就是適合的錘子。如果你的任務(wù)包含較為復(fù)雜的邏輯,而且數(shù)據(jù)結(jié)構(gòu)復(fù)雜,那么你需要用ruby/python之類(lèi)的語(yǔ)言編寫(xiě)腳本。

看完上述內(nèi)容,你們對(duì)Shell腳本編寫(xiě)的可靠建議有哪些有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI