作為一枚Linux嵌入式程序猿,寫shell腳本也是經常碰到的工作,在這個過程中或多或少踩過一些坑,也積累了一些經驗,在此分享給大家,希望能對大家有點幫助。
1. 指定bash
我們知道在shell 腳本的第一行,都應該指定bash,那#!之后到底應該是什么呢?
這個問題估計不同的人的回答可能都不一樣。我見過/usr/bin/env bash,也見過/bin/bash,還有/usr/bin/bash,還有/bin/sh,還有/usr/bin/env sh。我自己也用過其中過的幾個,其實在很多情況下,以上幾種寫法效果都是相同的。但是,坑我們的往往就是少數情況~
如果恰好碰到系統的默認shell不是bash怎么辦?比如某Linux發行版的某個版本,默認的 sh 就不是 bash。如果系統的bash不是在/usr/bin/bash怎么辦?
還有關于bash和sh的一些小區別:
假設我們寫一小段腳本,看看運行結果!
#!/bin/sh
source mlryj.sh
echo “hello world!”
執行結果:
然后我們將腳本改成這樣
#!/bin/bash
source mlryj.sh
echo “hello world!”
執行結果:
從結果看,在#!/bin/sh的情況下,在mlryj.sh這個腳本不存在的情況下,source不成功,不會運行source后面的代碼。
而在#!/bin/bash的情況下,雖然source不成功,但是還是運行了source后面的echo語句。
為什么會這樣呢?
接下來我們看一看/bin/sh是個什么東西。
從上面可以看到,sh只是bash的一個軟鏈接,在一般的linux系統當中,sh調用執行腳本相當于打開了bash的posix模式,也就是說 /bin/sh 相當于 /bin/bash --posix。posix的特定規范之一是,當某行代碼出錯時,便不繼續往下解釋。
把腳本改成如下圖所示的格式,我們得到運行結果和#!/bin/sh是一樣的。
#!/bin/bash --posix
source mlryj.sh
echo “hello world!”
推薦大家使用 /usr/bin/env bash 和 /bin/bash。前者通過env添加一個中間層,讓env在PATH中搜索bash;后者畢竟是有官方背書的,約定俗成的bash位置。但是當腳本出現了錯誤怎么辦呢?請看Tip2。
2. set -e 和 set -x
好了,關于指定bash已經完成了。接下來該開始寫shell腳本第二行、第三行。
小編建議:在你開始構思并寫下具體的代碼邏輯之前,先插入一行“set -e”和一行“set -x”。
set -x會在執行每一行shell腳本時,把執行的內容輸出來。它可以讓你看到當前執行的情況,里面涉及的變量也會被替換成實際的值。
set -e會在執行出錯時結束程序,就像其他語言中的“拋出異常”一樣。
這兩個組合在一起,可以在debug的時候替自己節省許多時間。出于防御性編程的考慮,有必要在寫第一行具體的代碼之前就插入它們。捫心自問,寫代碼的時候能夠一次寫對的次數有多少?大多數代碼,在提交之前,通常都經歷過反復調試修改的過程。與其在焦頭爛額之際才引入這兩個配置,不如一開始就給調試留下余地。在代碼終于可以提交之后,再考慮是否保留它們也不遲。
#!/bin/sh
set -x
set -e
source mlryj.sh
echo “hello world!”
運行結果如下:
3. shellcheck
加了set -x和set -e后,現在我已經有了shell開始的三行代碼,但是具體的業務邏輯一行都沒寫。是不是該開始寫了?
且慢!工欲善其事,必先利其器。小編先給各位介紹一個shell腳本編寫神器:shellcheck
很慚愧,雖然這幾年寫了一些shell腳本,但是真正寫起來的時候還是有很多語法記不清楚。這時候就要依仗shellcheck了。
shellcheck除了可以提醒語法問題以外,還能檢查出shell腳本編寫常見的錯誤代碼。相信我,使用shellcheck會給我們的shell編寫能力帶來了巨大的飛躍的。
安裝方法如下圖:
安裝完成后可以通過shellcheck -V查看當前安裝成功的版本號。如下圖:
雖然我們技能不如別人,但是我們可以升級裝備,在裝備上趕上并超過對方啊!有了shellcheck加持就好比真三中諸葛亮帶上了飛鞋,郭嘉買了孫子兵法,哈哈哈~~
4. 注意local
首先我們來看一下下面兩段代碼及運行結果,代碼段1:
#!/bin/bash
set -x
set -e
function wgytest()
{
a=$1
echo a
}
wgytest shell
echo “hello world!”
echo $a
運行結果如下圖所示:
代碼段2:
#!/bin/bash
set -x
set -e
function wgytest()
{
local a=$1
echo a
}
wgytest shell
echo “hello world!”
echo $a
運行結果如下圖所示:
從上面的代碼可以看出,我們只是將函數wgytest中的變量a加了local限定詞,運行結果可以看出,第二段代碼最后的echo并沒有輸出變量a的內容。
這是因為在bash,如果不加local限定詞,變量默認都是全局的。在頂級作用域里,是否是全局變量并不重要。但是在函數里面,聲明一個全局變量可能會污染到其他作用域,尤其在你根本沒有注意到這一點的情況下。當我們開始把重復的邏輯提煉成函數,這時就有可能會掉到bash的這一個坑里。所以,對于在函數內聲明的變量,小編的建議是記得加上local限定詞。
5. 寫在最后
以上幾條都是具體的建議,這條來點虛的。
雖然使用shell可以方便快捷地實現各種復雜的功能,但也還是不得不依靠grep、sed、awk等各種工具把它們粘合在一起。實際上由于缺乏完善的數據結構以及一致的API,shell腳本在處理復雜的邏輯上還是顯得力不從心。如果你的任務包含較為復雜的邏輯,而且數據結構復雜,那么建議使用python之類的語言編寫腳本。
程序猿無論寫什么代碼,都要三思而行,切忌粗心大意。畢竟許多時候,我們的一個粗心就會給整個系統帶來一個悲劇,其實復雜的腳本也是發端于幾行小小的命令。一開始寫腳本的人,也許以為它只是一次性任務。代碼里難免對一些外部條件有些假定,在當時也許是正常的,但是隨著外部環境的變化,這些就成了隱藏的雷。這就需要在編寫的時候辨清哪些是會變的依賴、哪些是腳本正常運行所不可或缺的。同時要有防御性編程的意識,給自己的代碼一道護城河。
-
Linux
+關注
關注
87文章
11123瀏覽量
207919 -
Shell
+關注
關注
1文章
359瀏覽量
23192
發布評論請先 登錄
相關推薦
評論