久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

linux中Shell腳本編程規范是什么

166次閱讀
沒有評論

共計 7263 個字符,預計需要花費 19 分鐘才能閱讀完成。

這篇文章主要介紹了 linux 中 Shell 腳本編程規范是什么,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓丸趣 TV 小編帶著大家一起了解一下。

代碼風格規范

開頭有“蛇棒”

所謂 shebang 其實就是在很多腳本的第一行出現的以 #! 開頭的注釋,他指明了當我們沒有指定解釋器的時候默認的解釋器,一般可能是下面這樣:

#!/bin/bash

當然,解釋器有很多種,除了 bash 之外,我們可以用下面的命令查看本機支持的解釋器:

$ cat /etc/shells #/etc/shells: valid login shells /bin/sh /bin/dash /bin/bash /bin/rbash /usr/bin/screen

當我們直接使用./a.sh 來執行這個腳本的時候,如果沒有 shebang,那么它就會默認用 $SHELL 指定的解釋器,否則就會用 shebang 指定的解釋器。

這種方式是我們推薦的使用方式。

代碼有注釋

注釋,顯然是一個常識,不過這里還是要再強調一下,這個在 shell 腳本里尤為重要。因為很多單行的 shell 命令不是那么淺顯易懂,沒有注釋的話在維護起來會讓人尤其的頭大。

注釋的意義不僅在于解釋用途,而在于告訴我們注意事項,就像是一個 README。

具體的來說,對于 shell 腳本,注釋一般包括下面幾個部分:

 shebang

  腳本的參數

  腳本的用途

  腳本的注意事項

  腳本的寫作時間,作者,版權等

  各個函數前的說明注釋

  一些較復雜的單行命令注釋

參數要規范

這一點很重要,當我們的腳本需要接受參數的時候,我們一定要先判斷參數是否合乎規范,并給出合適的回顯,方便使用者了解參數的使用。

最少,最少,我們至少得判斷下參數的個數吧:

if [[ $# != 2 ]];then echo  Parameter incorrect.  exit 1 fi

變量和魔數

一般情況下我們會將一些重要的環境變量定義在開頭,確保這些變量的存在。

source /etc/profile export PATH=”/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/apps/bin/”

這種定義方式有一個很常見的用途,最典型的應用就是,當我們本地安裝了很多 java 版本時,我們可能需要指定一個 java 來用。那么這時我們就會在腳本開頭重新定義 JAVA_HOME 以及 PATH 變量來進行控制。同時,一段好的代碼通常是不會有很多硬編碼在代碼里的“魔數”的。如果一定要有,通常是用一個變量的形式定義在開頭,然后調用的時候直接調用這個變量,這樣方便日后的修改。

縮進有規矩

對于 shell 腳本,縮進是個大問題。因為很多需要縮進的地方 (比如 if,for 語句) 都不長,所有很多人都懶得去縮進,而且很多人不習慣用函數,導致縮進功能被弱化。

其實正確的縮進是很重要的,尤其是在寫函數的時候,否則我們在閱讀的時候很容易把函數體跟直接執行的命令搞混。

常見的縮進方法主要有”soft tab”和”hard tab”兩種。

  所謂 soft tab 就是使用 n 個空格進行縮進(n 通常是 2 或 4)

  所謂 hard tab 當然就是指真實的 \t 字符

  這里不去撕哪種方式最好,只能說各有各的優劣。反正我習慣用 hard tab。

  對于 if 和 for 語句之類的,我們最好不要把 then,do 這些關鍵字單獨寫一行,這樣看上去比較丑。。。

命名有標準

所謂命名規范,基本包含下面這幾點:

  文件名規范,以.sh 結尾,方便識別

  變量名字要有含義,不要拼錯

  統一命名風格,寫 shell 一般用小寫字母加下劃線

編碼要統一

在寫腳本的時候盡量使用 UTF- 8 編碼,能夠支持中文等一些奇奇怪怪的字符。不過雖然能寫中文,但是在寫注釋以及打 log 的時候還是盡量英文,畢竟很多機器還是沒有直接支持中文的,打出來可能會有亂碼。這里還尤其需要注意一點,就是當我們是在 windows 下用 utf- 8 編碼來寫 shell 腳本的時候,一定要注意這個 utf- 8 是否是有 BOM 的。默認情況下 windows 判斷 utf- 8 格式是通過在文件開頭加上三個 EF BB BF 字節來判斷的,但是在 Linux 中默認是無 BOM 的。因此如果我們是在 windows 下寫腳本的時候,一定要注意將編碼改成 Utf- 8 無 BOM,一般用 notepad++ 之類的編輯器都能改。否則,在 Linux 下運行的時候就會識別到開頭的三個字符,從而報一些無法識別命令的錯。當然,對于跨平臺寫腳本還有一個比較常見的問題就是換行符不同。windows 默認是 \r\n 而 unix 下是 \n。不過有兩個小工具可以非常方便的解決這個問題:dos2unix,unix2dos。

權限記得加

這一點雖然很小,但是我個人卻經常忘記,不加執行權限會導致無法直接執行,有點討厭。。。

日志和回顯

日志的重要性不必多說,能夠方便我們回頭糾錯,在大型的項目里是非常重要的。

如果這個腳本是供用戶直接在命令行使用的,那么我們最好還要能夠在執行時實時回顯執行過程,方便用戶掌控。

有時候為了提高用戶體驗,我們會在回顯中添加一些特效,比如顏色啊,閃爍啊之類的,具體可以參考 ANSI/VT100 Control sequences 這篇文章的介紹。

密碼要移除

不要把密碼硬編碼在腳本里,不要把密碼硬編碼在腳本里,不要把密碼硬編碼在腳本里。

重要的事情說三遍,尤其是當腳本托管在類似 Github 這類平臺中時。。。

太長要分行

在調用某些程序的時候,參數可能會很長,這時候為了保證較好的閱讀體驗,我們可以用反斜杠來分行:

./configure \  ndash;prefix=/usr \  ndash;sbin-path=/usr/sbin/nginx \  ndash;conf-path=/etc/nginx/nginx.conf \

注意在反斜杠前有個空格。

編碼細節規范

代碼有效率

在使用命令的時候要了解命令的具體做法,尤其當數據處理量大的時候,要時刻考慮該命令是否會影響效率。

比如下面的兩個 sed 命令:

sed -n  1p  file sed -n  1q  file

他們的作用一樣,都是獲取文件的第一行。但是第一條命令會讀取整個文件,而第二條命令只讀取第一行。當文件很大的時候,僅僅是這樣一條命令不一樣就會造成巨大的效率差異。

當然,這里只是為了舉一個例子,這個例子真正正確的用法應該是使用 head -n1 file 命令。。。

勤用雙引號

幾乎所有的大佬都推薦在使用”$”來獲取變量的時候最好加上雙引號。

不加上雙引號在很多情況下都會造成很大的麻煩,為什么呢?舉一個例子:

#!/bin/sh # 已知當前文件夾有一個 a.sh 的文件  var= *.sh  echo $var echo  $var

他的運行結果如下:

a.sh *.sh

為啥會這樣呢?其實可以解釋為他執行了下面的命令:

echo *.sh echo  *.sh

在很多情況下,在將變量作為參數的時候,一定要注意上面這一點,仔細體會其中的差異。上面只是一個非常小的例子,實際應用的時候由于這個細節導致的問題實在是太多了。。。

巧用 main 函數

我們知道,像 java,C 這樣的編譯型語言都會有一個函數入口,這種結構使得代碼可讀性很強,我們知道哪些直接執行,那些是函數。但是腳本不一樣,腳本屬于解釋性語言,從第一行直接執行到最后一行,如果在這當中命令與函數糅雜在一起,那就非常難讀了。

用 python 的朋友都知道,一個合乎標準的 python 腳本大體上至少是這樣的:

#!/usr/bin/env python def func1(): pass def func2(): pass if __name__== __main__ : func1() func2()

他用一個很巧妙的方法實現了我們習慣的 main 函數,使得代碼可讀性更強。

在 shell 中,我們也有類似的小技巧:

#!/usr/bin/env bash func1(){ #do sth } func2(){ #do sth } main(){ func1 func2 } main  $@

我們可以采用這種寫法,同樣實現類似的 main 函數,使得腳本的結構化程度更好。

考慮作用域

shell 中默認的變量作用域都是全局的,比如下面的腳本:

#!/usr/bin/env bash var=1 func(){ var=2 } func echo $var

他的輸出結果就是 2 而不是 1,這樣顯然不符合我們的編碼習慣,很容易造成一些問題。

因此,相比直接使用全局變量,我們最好使用 local readonly 這類的命令,其次我們可以使用 declare 來聲明變量。這些方式都比使用全局方式定義要好。

函數返回值

在使用函數的時候一定要注意,shell 中函數的返回值只能是整數,估計是因為一般情況下一個函數的返回值通常表示這個函數的運行狀態,所以一般都是 0 或者是1就夠了,因此就設計成了這樣。不過,如果非得想傳遞字符串,也可以通過下面變通的方法:

func(){ echo  2333  } res=$(func) echo  This is from $res.

這樣,通過 echo 或者 print 之類的就可以做到傳一些額外參數的目的。

間接引用值

什么叫間接引用?比如下面這個場景:

VAR1= 2323232  VAR2= VAR1

我們有一個變量 VAR1,又有一個變量 VAR2,這個 VAR2 的值是 VAR1 的名字,那么我們現在想通過 VAR2 來獲取 VAR1 的值,這時候應該怎么辦呢?

比較土鱉的方法是這樣:

eval echo \$$VAR2

啥意思呢?其實就是構造了一個字符串 echo XXX,這個 XXX 就是 XXX”,這個 XXX 就是 VAR2 的值 VAR1,然后再用 eval 強制解析,這樣就做到了變相取值。

這個用法的確可行,但是看起來十分的不舒服,很難直觀的去理解,我們并不推薦。而且事實上我們本身就不推薦使用 eval 這個命令。

比較舒服的寫法是下面這樣:

echo ${!VAR1}

通過在變量名前加一個! 就可以做到簡單的間接引用了。

不過需要注意的是,用上面的方法,我們只能夠做到取值,而不能做到賦值。如果想要做到賦值,還要老老實實的用 eval 來處理:

VAR1=VAR2 eval $VAR1=233 echo $VAR2

巧用 heredocs

所謂 heredocs,也可以算是一種多行輸入的方法,即在””后定一個標識符,接著我們可以輸入多行內容,直到再次遇到標識符為止。

使用 heredocs,我們可以非常方便的生成一些模板文件:

cat /etc/rsyncd.conf   EOF log file = /usr/local/logs/rsyncd.log transfer logging = yes log format = %t %a %m %f %b syslog facility = local3 EOF

學會查路徑

很多情況下,我們會先獲取當前腳本的路徑,然后一這個路徑為基準,去找其他的路徑。通常我們是直接用 pwd 以期獲得腳本的路徑。

不過其實這樣是不嚴謹的,pwd 獲得的是當前 shell 的執行路徑,而不是當前腳本的執行路徑。

正確的做法應該是下面這兩種:

script_dir=$(cd $(dirname $0)   pwd) script_dir=$(dirname $(readlink -f $0 ))

應當先 cd 進當前腳本的目錄然后再 pwd,或者直接讀取當前腳本的所在路徑。

代碼要簡短

這里的簡短不單單是指代碼長度,而是只用到的命令數。原則上我們應當做到,能一條命令解決的問題絕不用兩條命令解決。這不僅牽涉到代碼的可讀性,而且也關乎代碼的執行效率。

最最經典的例子如下:

cat /etc/passwd | grep root grep root /etc/passwd

cat 命令最為人不齒的用法就是這樣,用的沒有任何意義,明明一條命令可以解決,他非得加根管道。。。

其實代碼簡短在還能某種程度上能保證效率的提升,比如下面的例子:

#method1 find . -name  *.txt  |xargs sed -i s/233/666/g find . -name  *.txt  |xargs sed -i s/235/626/g find . -name  *.txt  |xargs sed -i s/333/616/g find . -name  *.txt  |xargs sed -i s/233/664/g #method1 find . -name  *.txt  | xargs sed -i  s/233/666/g;s/235/626/g;s/333/616/g;s/233/664/g

這兩種方法做的事情都一樣,就是查找所有的.txt 后綴的文件并做一系列替換。前者是多次執行 find,后者是執行一次 find,但是增加了 sed 的模式串。第一種更直觀一點,但是當替換的量變大的時候,第二種的速度就會比第一種快很多。這里效率提升的原因,就是第二種只要執行一次命令,而第一種要執行多次。并且,巧用 xargs 命令,我們還可以十分方便的進行并行化處理:

find . -name  *.txt  |xargs -P $(nproc) sed -i  s/233/666/g;s/235/626/g;s/333/616/g;s/233/664/g

通過 - P 參數指定并行度,可以進一步加快執行效率。

命令并行化

當我們需要充分考慮執行效率時,我們可能需要在執行命令的時候考慮并行化。shell 中最簡單的并行化是通過””以及”wait”命令來做:

func(){ #do sth } for((i=0;i i++))do func   done wait

當然,這里并行的次數不能太多,否則機器會卡死。稍微正確的做法比較復雜,以后再討論,如果圖省事可以使用 parallel 命令來做,或者是用上面提到的 xargs 來處理。

全文本檢索

我們知道,當我們想在文件夾下所有的 txt 文件中檢索某一個字符串 (比如 233) 的時候,我們可能會用類似這樣的命令:

find . -name  *.txt  -type f | xargs grep 2333

很多情況下,這個命令會想我們所想的找到對應的匹配行,但是我們需要注意兩個小問題。

find 命令會符合要求的匹配文件名,但是如果文件名包含空格,這時候將文件名傳給 grep 的時候就會有問題,這個文件就會被當成兩個參數,這時候就要加一層處理,保證用空格分開的文件名不會被當成兩個參數:

find . -type f|xargs -i echo  {} |xargs grep 2333

有時候,文件的字符集可能跟終端的字符集不一致,這時候就會導致 grep 在搜索時將文件當成二進制文件從而報 binary file matches 之類的問題。這時候要么用 iconv 之類的字符集轉換工具將字符集進行切換,要么就在不影響查找的情況下對 grep 加 - a 參數,將所有文件看成文本文件:

find . -type f|xargs grep -a 2333

使用新寫法

這里的新寫法不是指有多厲害,而是指我們可能更希望使用較新引入的一些語法,更多是偏向代碼風格的,比如

盡量使用 func(){}來定義函數,而不是 func{}

盡量使用 [[]] 來代替[]

盡量使用 $()將命令的結果賦給變量,而不是反引號

在復雜的場景下盡量使用 printf 代替 echo 進行回顯

事實上,這些新寫法很多功能都比舊的寫法要強大,用的時候就知道了。

其他小 tip

考慮到還有很多零碎的點,就不一一展開了,這里簡單提一提。

路徑盡量保持絕對路徑,絕多路徑不容易出錯,如果非要用相對路徑,最好用./ 修飾

優先使用 bash 的變量替換代替 awk sed,這樣更加簡短

簡單的 if 盡量使用 ||,寫成單行。

比如[[x 2]] echo x

當 export 變量時,盡量加上子腳本的 namespace,保證變量不沖突

會使用 trap 捕獲信號,并在接受到終止信號時執行一些收尾工作

使用 mktemp 生成臨時文件或文件夾

利用 /dev/null 過濾不友好的輸出信息

會利用命令的返回值判斷命令的執行情況

使用文件前要判斷文件是否存在,否則做好異常處理

不要處理 ls 后的數據(比如 ls -l | awk lsquo;{ print $8} rsquo;),ls 的結果非常不確定,并且平臺有關

讀取文件時不要使用 for loop 而要使用 while read

使用 cp - r 命令復制文件夾的時候要注意如果目的文件夾不存在則會創建,如果存在則會復制到該文件的子文件夾下

靜態檢查工具 shellcheck

概述

為了從制度上保證腳本的質量,我們最簡單的想法大概就是搞一個靜態檢查工具,通過引入工具來彌補開發者可能存在的知

市面上對于 shell 的靜態檢查工具還真不多,找來找去就找到一個叫 shellcheck 的工具,開源在 github 上,有 8K 多的 star,看上去還是十分靠譜的。我們可以去他的主頁了解具體的安裝和使用信息。

安裝

這個工具的對不同平臺的支持力度都很大,他至少支持了 Debian,Arch,Gentoo,EPEL,Fedora,OS X,openSUSE 等等各種的平臺的主流包管理工具。安裝方便。具體可以參照安裝文檔

集成

既然是靜態檢查工具,就一定可以集成在 CI 框架里,shellcheck 可以非常方便的集成在 Travis CI 中,供以 shell 腳本為主語言的項目進行靜態檢查。

樣例

在文檔的 Gallery of bad code 里,也提供了非常詳細的“壞代碼”的標準,具有非常不錯的參考價值,可以在閑下來的時候當成”Java Puzzlers“之類的書來讀讀還是很愜意的。

感謝你能夠認真閱讀完這篇文章,希望丸趣 TV 小編分享的“linux 中 Shell 腳本編程規范是什么”這篇文章對大家有幫助,同時也希望大家多多支持丸趣 TV,關注丸趣 TV 行業資訊頻道,更多相關知識等著你來學習!

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-25發表,共計7263字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 富民县| 宜兰市| 新邵县| 济宁市| 泸西县| 西充县| 烟台市| 林西县| 尼木县| 遂溪县| 营口市| 长宁县| 莲花县| 营山县| 历史| 嵩明县| 白水县| 厦门市| 渝北区| 泗洪县| 弋阳县| 岑溪市| 呼伦贝尔市| 和平县| 托克逊县| 全州县| 玉林市| 临武县| 云浮市| 北宁市| 襄垣县| 库车县| 曲阜市| 通州市| 砚山县| 安阳市| 九台市| 合山市| 定西市| 香河县| 连平县|