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