編寫快速安全Bash腳本的建議
昨天我和一些朋友聊起B(yǎng)ash,我意識到:即使我已經(jīng)使用Bash十多年了,現(xiàn)在還有一些基礎(chǔ)的雜項(xiàng),我理解的并不是很清晰。 像往常一樣,我認(rèn)為我應(yīng)該寫一個(gè)博文。
我們會包含:
-
一些bash基礎(chǔ)知識(“你怎么寫一個(gè)for循環(huán)”)
-
雜項(xiàng)事宜(“總是引用你的bash變量”)
-
bash腳本安全提示(“總是使用set -u”)
如果你編寫shell腳本,并且你沒有閱讀這篇文章中其他任何內(nèi)容,你應(yīng)該知道有一個(gè)shell腳本校驗(yàn)工具(linter),叫做 shellcheck 。使用它來使您的shell腳本更好!
我們會像討論編程語言一樣討論bash,因?yàn)?,怎么說呢,它就是。 這篇文章的目標(biāo)不是bash編程詳解。我不會在bash中做復(fù)雜的編程,也真的不計(jì)劃學(xué)習(xí)如何去做。 但是,經(jīng)過今天的思考之后,我認(rèn)為明確整理下bash編程語言的一些基礎(chǔ)知識是有用的。bash編程語言與我使用過的其他編程語言有著很大的不同。
我真的曾認(rèn)為我已經(jīng)知道這些東西了,但是通過寫這篇文章我依舊學(xué)到了一些東西,也許你也會有所收獲。
變量賦值
在bash中變量賦值按照下面的方式:
VARIABLE=2
并且你可以使用$VARIABLE(變量名)來引用變量。需要注意的是不要在=運(yùn)算符的兩邊放置空格符,比如VARIABLE= 2、VARIABLE = 2、或者VARIABLE =2,這并不是語法錯(cuò)誤,但是將會做完全不需要的事情(比如試圖運(yùn)行一個(gè)名字為2的程序,并將環(huán)境變量VARIABLE設(shè)置為空字符串)。
Bash變量并不要求全部大寫,但是通常是大寫的。
大多數(shù)你所使用的bash變量都是字符串。在bash中也有一些數(shù)組變量,但我并不是完全理解它們。
使用${}引用變量
有時(shí)某些變量,內(nèi)容為file.txt,并且我想這樣使用它:
mv $MYVAR $MYVAR__bak # wrong!
這段代碼是無法工作的!它會去查找 MYVAR__bak變量,但這并不是一個(gè)真實(shí)存在的變量。
為了避免類似問題,你需要知道的僅僅是 ${MYVAR}和$MYVAR是一回事。所以你可以這樣運(yùn)行指令:
mv $MYVAR ${MYVAR}__bak # right!
全局變量,局部變量和環(huán)境變量
Bash有3種變量。我一般先想到(可能也是最常用)的是 環(huán)境變量 。
Linux上的每個(gè)進(jìn)程實(shí)際上都有環(huán)境變量(您可以運(yùn)行env查看當(dāng)前設(shè)置的變量),但在Bash中,它們更易于訪問。要查看名為MYVAR的環(huán)境變量,可以運(yùn)行
echo "$MYVAR"
要設(shè)置環(huán)境變量,您需要使用export關(guān)鍵字:
export MYVAR=2
設(shè)置環(huán)境變量時(shí),所有子進(jìn)程將看到該環(huán)境變量。所以如果你運(yùn)行export MYVAR=2; python test.py,python程序?qū)YVAR設(shè)置為2。
第二種變量是 全局變量 。同樣像上面那樣賦值。
MYVAR=2
在其他編程語言中他們表現(xiàn)得像全局變量。
還有 局部變量 ,它們的作用域只能存在于bash函數(shù)中。 我基本上從來沒有使用過這樣的函數(shù)(不像我寫的其他編程語言),我從來沒有使用過局部變量。
for循環(huán)
以下是我在bash中編寫循環(huán)的方法。 此循環(huán)將從1打印到10。
for i in `seq 1 10` # you can use {1..10} instead of `seq 1 10` do echo "$i" done
如果你想用一行代碼寫這個(gè)循環(huán),可以這樣寫:
for i in `seq 1 10`; do echo $i; done
我覺得這是不可能記住的(你要怎么記住在 seq 1 10 之后有一個(gè)分號,但是在 do 之后卻沒有了),所以我不會去記它。
你也可以寫while循環(huán),但我從來沒有這樣寫過。
有個(gè)很酷的事情是你可以遍歷另一個(gè)命令的輸出。seq 1 10 將數(shù)字從1到10(每行一個(gè))打印,這個(gè)for循環(huán)只是提取該輸出并遍歷它。我就經(jīng)常用這種方法。
您也可以使用反引號或$()來插入命令的輸出。
OUTPUT=`command` # or OUTPUT=$(command)
if 語句
在 bash 中的 If 語句是相當(dāng)讓人討厭去記它。你必須放在這些方括號中,而在方括號之間必須有空格,否則它不起作用。[[ 和 [ 方括號(雙/單) 都工作。 這里我們真正進(jìn)入 bash 的奇怪領(lǐng)域:[ 是一個(gè)程序(/usr/bin/[)但 [[ 是 bash 語法。[[ 更好。
if [[ "vulture" = "panda" ]]; then echo expression evaluated as true else echo expression evaluated as false fi
此外,您可以檢查“此文件存在”,“此目錄存在”等內(nèi)容。例如,您可以檢查文件 /tmp/awesome.txt 是否存在,如下:
If [[ -e /tmp/awesome.txt ]]; then echo "awesome" fi
這通常是有用的,但我必須每次查找語法。
如果您想嘗試用命令行,可以使用 test 命令,例如 test -e /tmp/awesome.txt 。 它成功會返回0,否則返回錯(cuò)誤。
***一件事是為什么[[比[好:如果你使用[[,那么你可以使用<做比較,它不會變成文件重定向。
$ [ 3 < 4 ] && echo "true" bash: 4: No such file or directory $ [[ 3 < 4 ]] && echo "true" true
還有一個(gè)額外的***一件關(guān)于 if 的事:我今天學(xué)到是不需要通過[[或者[去使用 if 語句:任何有效的命令都會工作。 所以你可以這樣做
if grep peanuts food-list.txt then echo "allergy allert!" fi
函數(shù)不是那么難
在 bash 中定義和調(diào)用函數(shù)(特別是沒有參數(shù))是非常容易的。
my_function () { echo "This is a function"; } my_function #調(diào)用函數(shù)
總是引用你的變量
另一個(gè) bash 技巧:絕不使用一個(gè)沒有引用的變量。 看看這個(gè)看似合理的 shell 腳本:
X="i am awesome" Y="i are awesome" if [ $X = $Y ]; then echo awesome fi
如果你嘗試運(yùn)行這個(gè)腳本,你會得到不可理喻的錯(cuò)誤消息 script.sh: line 3: [: too many arguments. 什么?
Bash 解釋這個(gè) if 語句為 if [ i am awesome == i are awesome],這是6個(gè)字符串 (i, am, awesome, i, are, awesome) 無意義的 if 語句。 正確的寫法是
X="i am awesome" Y="i are awesome" if [ "$X" = "$Y" ]; then #我放置引號因?yàn)槲抑纀ash會背叛我,如果我不放的話 echo awesome fi
有些情況下,只要使用 $ X 而不是 “$ X” 就可以,但是您可以知道何時(shí)可以,何時(shí)不行嗎? 我肯定不能。 總是引用你的 bash 變量,你會更快樂的。
返回代碼, &&, 和 `||
每個(gè) Unix 程序都有一個(gè)“返回代碼”,它是一個(gè)從0到127的整數(shù)。0表示成功,其他都意味著失敗。 這在 bash 中是有作用的,因?yàn)椋河袝r(shí)我從命令行運(yùn)行一個(gè)程序,并希望僅在***個(gè)程序成功的情況下運(yùn)行第二個(gè)程序。
你可以用 && 實(shí)現(xiàn)!
例如:create_user && make_home_directory。 這將運(yùn)行 create_user ,檢查返回代碼,然后僅在返回代碼為0時(shí)運(yùn)行 make_home_directory。
這與 create_user; make_home_directory 不同,無論 create_user 的返回代碼是什么,都將運(yùn)行 make_home_directory。
你也可以使用create_user || make_home_directory,只有create_user運(yùn)行失敗才運(yùn)行make_home_directory 。 這在技術(shù)領(lǐng)域中非常巧妙。
后臺進(jìn)程
我不會在此談及太多關(guān)于 job 控制的內(nèi)容,但是:你可以像下面這樣啟動后臺進(jìn)程
long_running_command &
如果你后來后悔將進(jìn)程放到后臺,并希望把它帶調(diào)回前臺,你可以用 fg 來做到這一點(diǎn)。 如果不止一個(gè)進(jìn)程,您可以使用 jobs 查看所有后臺進(jìn)程。由于某種原因,fg 需要一個(gè) “job ID”(這就是 jobs 打印輸出的)而不是一個(gè) PID。 誰知道 Bash 為什么這樣子呢。
另外,如果你在后臺運(yùn)行太多的進(jìn)程,內(nèi)置等待命令將等到它們都返回。
說到后悔 - 如果你不小心在錯(cuò)誤的終端啟動一個(gè)進(jìn)程,Nelson Elhage 有一個(gè)很棒的項(xiàng)目叫做 reptyr ,可以保存你的進(jìn)程并將其移到屏幕會話或者某些其他東西中。