Shell與腳本
shell是Linux操作系統的用戶接口,我們經常需要編寫腳本讓操作系統自動執行一系列指令的需求,本文將簡單介紹開發shell腳本的所需的語言特性。
shell腳本是指令序列,其指令可以直接在終端中執行。同樣地,終端中的指令也可以寫到腳本中。
腳本文件通常以.sh
作為後綴名,第一行以#!
開頭指定執行腳本的程序:
#!/usr/bin/bash
以#!
開頭的第一行被稱為Hashbang或Shebang。 而#
是shell腳本中的行註釋符。
通常有三種執行腳本的方式:
sh start.sh
: 在終端中創建一個sh子進程執行腳本, 執行者需要擁有腳本的讀權限。
該方式實際上是將腳本路徑作為參數傳遞給了sh
source start.sh
: 在終端中執行腳本,相當於將腳本中的指令逐條復制到終端執行。
腳本中局部變量將保留在終端環境變量中, 腳本的pid和工作目錄等環境也與終端一致。./start.sh
: 根據HashBang指定的程序,在子進程中執行腳本。
sh
命令有一些有用的選項幫助我們開發和調試腳本:
sh -n start.sh
:對腳本進行語法檢查, 不實際執行腳本sh -x start.sh
: 把將要執行的命令輸出到stderr便於進行調試
變量
shell中變量是弱類型的, 變量名只能包含字母、數字或下劃線"_",首字符只能為字母。
shell主要面向文本處理而非數據計算,因此變量默認類型為字符串型。
A=abc
echo $A
變量在使用前無需聲明,在為變量賦值時=
左右不能添加空格。
A = abc
會被shell解釋為執行指令A
,參數為=
和abc
。
$
為變量標誌符, echo $A
指令將顯示變量A的內容abc
, 為了明確指定變量名也可以寫作${A}
。
A=a
AB=ab
echo ${A}B
$(cmd)
可以把命令的輸出作為返回值, 如:
PWD=$(pwd)
變量$PWD
存儲了當前的工作目錄路徑。
在shell中可以直接書寫字符串,但仍建議用單引號或雙引號標識字符串。
在單引號標識的字符串中$
不被作為變量標識符, 而雙引號則會將$
替換為變量內容。
A="abc"
echo ‘$A‘ # $A
echo "$A" # abc
字符串拼接不需要任何運算符,只需要將它們寫在一起即可:
A="abc"
B="123"
echo "$A+$B" # abc+123
echo "$A$B" # abc123
echo "$Adef" # abcdef
全局變量
變量按照作用域可以分為局部變量和全局變量,上文示例中定義的變量都是局部變量, 作用域僅限執行腳本的進程。
子進程可以繼承父進程的全局變量,export
指令用於定義全局變量:
export A=abc
整型變量
shell僅支持整型計算, declare
命令可以聲明整型變量,let
指令用於算術運算:
declare -i a=1
let a=a+1
echo $a # 2
let a+=1
echo $a # 3
let
指令支持算術運算符包括:
+
:加法-
: 減法*
: 乘法/
: 除法**
: 乘方%
: 取余
let
指令也支持算術運算符對應的算術賦值運算符,如+=
。
數組
bash中可以使用圓括號定義數組,元素之間用空格分割,數組下標從1開始:
arr=(1 ‘a‘ "abc")
echo ${arr[1]}
也可以直接使用下標定義數組:
arr2[1]=1
arr2[2]=2
該方法同樣可以用於修改已存在的數組。
特殊變量
shell中預定義了一些特殊變量,通過這些變量可以獲得環境信息:
$$
: 執行腳本的進程ID(pid)$?
: 上一條命令的返回值$!
: 上一條後臺指令的執行進程的ID
上述變量在交互式終端中同樣有效。
還有一些變量可以獲得執行腳本時傳入的參數:
$0
: 腳本的文件名$1
~$n
: 傳給腳本的第n個參數$#
: 傳入參數的個數$@
: 參數列表$*
: 單個字符串形式的參數列表
流程控制
if
declare -i a=90
if [ $a -gt 80 ]; then
echo "A"
elif [ $a -lt 60 ]; then
echo "C"
else
echo "D"
fi
結束標誌fi
即是if
反寫, 我們還將在其它地方遇到bash的這種命名風格。
註意,[]
旁邊的空格不可省略。
-lt
, -gt
用於進行整型的大小比較:
-eq
: 等於(equal)-ne
: 不等於(not equal)-gt
: 大於(greater)-ge
: 大於等於(greater-equal)-lt
: 小於(less)-le
: 小於等於(less-equal)
進行復合邏輯判斷也很簡單:
if [ $a -gt 60 -a ( ! $a -gt 90 -o $a eq 91 ) ]; then
echo "make no sense"
fi
!
: 非-a
: 且and-o
: 或-o
邏輯運算遵循短路計算原則。
<
, >
等運算符在[]
中只能用於字符串的比較, 而在[[]]
中<
, >
可以用於整型和字符串的大小比較, 也可以使用&&
和||
來書寫邏輯表達式。
if的條件判斷不一定使用[]
或[[]]
表達式,它可以是任何一個命令。命令的返回值為0則if判斷為真, 非0判斷為假。
[]
和[[]]
轉義表達式也可以像普通指令一樣執行,判斷為真則返回0,假則返回非0值。
[ 2 -gt 1 -a 3 -lt 4 ] && echo ‘ok‘
除此之外,if還可以進行更多種類的條件判斷:
判斷字符串相等
if [ ${NAME} = ‘tmp‘ ]; then
echo "name is tmp"
fi
判斷文件是否存在
if [ -e tmp ]; then
echo "tmp exists"
fi
判斷tmp
是否存在,tmp
可以是目錄或文件。
判斷是否為普通文件
if [ -f tmp ]; then
echo "file tmp exists"
fi
判斷tmp
是否為文件,tmp
不能是目錄。
判斷是否為目錄
if [ -d tmp ]; then
echo "directory tmp exists"
fi
判斷是否具有執行權限
if [ -x tmp ]; then
echo "tmp is executable"
fi
不判斷文件是否可執行,只判斷是否擁有x
權限。 因此,tmp為有x權限的目錄時也會判斷為真。
類似的還有,-w
判斷是否擁有寫入權限, -r
判斷是否擁有讀取權限。
判斷是否為空文件
if [ -s tmp ]; then
echo "file is not empty"
fi
case
case類似於其它語言中的switch語句:
case ${NAME} in
‘a‘)
echo "name is a"
;;
‘b‘)
echo "name is b"
;;
*)
echo "other names"
;;
esac
從第一個匹配的標簽開始執行, 兩個標簽之間必須有;;
,*)
是其它標簽都不匹配時的默認標簽。
for
for循環可以遍歷一個序列:
for i in $(seq 1 10); do
echo $i
done
# echo: 1 2 3 ... 10
一些命令的輸出也可以作為序列:
for i in $(ls); do
echo $i
done
遍歷所有參數:
for arg in "$@"; do
echo $arg
done
另一種形式的for循環:
for (( i=0; i<100; i++)); do
echo $i
done
while
declare -i i=0
while [ $i -lt 10 ]; do
echo $i
i=$i+1
done
while(true)
這樣的死循環也很容易:
declare -i i=0
while ; do
echo $i
[ ! $i -lt 10 ] && break
i=$i+1
done
函數
shell提供了定義函數的功能, 函數就像是腳本中的子腳本:
range() {
for (( i=0; i<${1}; i++)); do
echo $i
done
return ${1}
}
range 100
函數同樣使用位置參數$1
~$n
來訪問參數,$0
為函數的名稱, $@
, $#
等變量的含義不變。
進程間通信
管道
管道用於將上一條指令的輸出作為下一條指令的輸入:
ls | grep ".zip"
xargs
有一些指令不支持使用管道傳遞參數,因此需要xargs
命令
find ~ | xargs ls
xargs會以空格為分隔符將輸入分隔為參數,然後將參數傳給ls。
重定向
重定向用於將命令的輸入輸出從標準流重定向到文件。標準流包括:
- stdin: 標準輸入流,文件描述符為0
- stdout: 標準輸出流,文件描述符1
- stderr: 標準錯誤流,文件描述符2
輸出到文件,覆蓋原有內容:
echo "hello" > 1.txt
輸出到文件, 追加到文件尾:
echo "hello" >> 1.txt
從文件輸入:
wc -l < 1.txt
重定向標準錯誤輸出流:
cmd 2> 2.txt
將標準錯誤輸出追加到文件:
cmd 2>> 2.txt
將標準錯誤和標準輸出一同重定向到文件:
cmd > 1.log 2>&1
2>&1
是將stderr重定向到stdout。
後臺執行
shell可以執行一行指令後立即返回, 返回後可以通過$?
變量獲得執行進程的ID:
$ sleep 10 &
[1] 79403
$ echo $!
79403
Shell與腳本