【讀書筆記】Linux命令行與Shell腳本編程大全

Linux命令行與Shell腳本編程大全

5.2 shell 的父子關係

命令分組

Command Grouping 主要有兩種形式:linux

  • 一種以小括號包括,命令之間以冒號分隔。也被稱爲 進程列表
    • 注意,至少要有兩條命令纔會被認爲是進程列表:(command;command[;command;command...])
  • 另外一種以大括號包括,語法爲 { command;[command;command;...] } ;
    • 注意與花括號之間必須留有空格,同時最後一個命令後仍然須要一個分號。

兩種命令分組的區別在於,使用進程列表將會建立出一個子shell來執行分組中的命令,而另外一種形式則不會如此。shell

協程

coproc 是一個 shell 關鍵字,其用法爲 coproc [name] command,它將在後臺生成一個子 shell,並在其中執行 command 。協程的名字默認爲 COPROC,當顯式命名時,command 部分應採用命令分組的形式(注意若使用進程列表,將產生嵌套的子 shell )。express

11.4 使用變量

命令替換

爲了將命令的輸出提取出來以賦給變量,可使用以下兩種形式執行命令:編程

  • 使用反引號字符(`)
  • $() 格式

下面的腳本將當前日期及時間嵌入到特定字串中輸出:bash

#!/bin/bash

my_var=`date`	# or my_var=$(date)
echo "The date and time are: " $my_var

11.7 執行數學運算

expr 命令

Bourne shell 提供了一個特別的命令,即expr,來處理數學表達式,該命令可以識別少數的數字及字符串操做符,此外該命令對數字的支持僅限於整數。而且,應注意到,在shell中使用時還須要對一些操做符進行轉義。ide

bash shell 中,爲了不轉義操做符帶來的麻煩,應採用這種形式 $[ operation ] 來執行數學表達式。函數

浮點運算

z shell 提供了完整的浮點數算術支持。而在bash中,爲了避開數學運算的整數限制,常見的方案是使用內建的bash計算器,即bcoop

下面是一個在腳本中使用bc的簡單例子:測試

#!/bin/bash

res=$( echo "scale=2; var1=3.1415; var1 / 2" | bc )
echo "Result is $res"

bc有一個內建變量 scale ,默認值爲0。該變量控制着在含有除法的浮點數運算中,結果所保留的小數位數。浮點乘法不受其影響。ui

12.4 test 命令

if-then 語句中的 test 命令
if command
then
	commands
fi
if command; then
	commands
fi

if-then 語句首先執行 command 部分並測試其退出狀態碼( exit status ),若是爲0,則執行 commands 部分。test 命令,提供了測試更多條件的途徑。test condition 若是 condition 成立,test 命令就會返回退出狀態碼 0,不然返回非零的退出狀態碼。

bash shell 提供了 test 命令的另外一種更簡便的寫法,注意 condition 與方括號間的空格是必須的。

if [ condition ]
then
	commands
fi
可判斷的三類條件
  • 數值比較
    • n1 -op n2 , op: eq ne ge gt le lt
    • 沒法處理浮點值
  • 字符串比較
    • str1 op str2, op: = != < >
    • -op str1, op: n z
    • 進行大小比較時,須要對 op 進行轉義,此外,該比較使用標準ASCII順序進行(sort命令使用的是系統的本地化語言設置中定義的排序順序。對於英語,本地化設置制定了在排序中小寫字母在大寫字母前)。
  • 文件比較
    • -op file, op: d e f r s w x O G
    • file1 -op file2, op: nt ot
    • 其中 s 檢查file是否存在並不是空;O 檢查file是否存在並屬當前用戶全部;G 檢查file是否存在並默認組與當前用戶的相同;nt 表示 newer then ,ot 表示 older then ,注意 nt 和 ot 不會關心文件是否存在。
複合測試條件

if-then 語句容許使用布爾邏輯來組合測試,有兩種布爾運算符可用:

[ condition1 ] op [ condition2 ], op: && ||

變量參與

若是測試中的操做數包含了變量,那麼建議使用引號將變量替換表達式引住。這是由於,若是變量爲空(這在使用命令替換給變量賦值時可能會發生),那麼變量替換將使得某些測試缺乏操做數,從而形成錯誤;或者若是變量的值中包含空格,那麼對於測試,將會出現非法或多餘的參數。

經過使用 [ "$var" op val ] 這種形式,能夠明確告知 test 命令,那裏有一個參數,即使參數是空值或包含空格。

12.6 if-then 的高級特性

用於數學表達式的雙括號

(( expression )),其中可以使用各類各樣的數學運算符,且不須要對諸如 < > 這樣的操做符進行轉義。

此外,因爲 expression 能夠是任意的數學賦值或比較表達式,故也可將雙括號命令用做給變量賦值的普通命令。

用於高級字符串處理功能的雙方括號

[[ expression ]],expression 使用了 test 命令中採用的標準字符串比較,同時提供了額外的特性:模式匹配,經過使用雙等號操做符來使用這一特性。

12.7 case 語句

case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default_commands;;
esac

shell 中的 case 不須要 "break" 語句便可跳轉出去。

13.1 for 命令

內部字段分隔符 ( Internal Field Separator )

IFS 環境變量定義了 bash shell 用做字段分隔符的一系列字符。默認的字段分隔符有:

  • 空格
  • 製表符
  • 換行符
for 命令的格式
for var in list
do
	commands
done
for var in list; do
	commands
done
C 語言風格的 for 命令

for (( variable assignment ; condition ; iteration process ))

注意,有些部分沒有遵循 bash shell 的標準:

  • 變量賦值能夠有空格
  • 條件中的變量不以 $ 開頭
  • 迭代過程的算式未用 expr 命令格式

下面是一個採用該風格但沒有實際意義的例子:

for (( a = 1, b = 10; a<=10 && b > 6; a++, b-- ))
do
	echo "$a - $b"
done
seq 命令

並非全部的 shell 都像 bash 同樣支持 C 語言風格的 for 命令,如此,就須要寫出一大串數字序列做爲 list ,不過,幸虧有 seq 命令能夠幫助咱們生成數字序列。

seq [option]... [first] [increment] last

默認 first 和 increment 爲 1 。

13.3 while 命令

while test_command
do
	commands
done

其中 test_command 與 if-then 語句中該部分的用法相同。此外,能夠定義多個 test_command,但只有最後一個測試命令的退出狀態碼被用來決定是否結束循環,注意,每一個測試命令都應出如今單獨的一行上。

下面是一個定義有多個 test_command 但沒有什麼實際意義的腳本:

#!/bin/bash

var=5
while echo $var
	[ $var -gt 0 ]
do
	echo "still in the loop"
	(( var = $var - 1 ))
done

輸出爲:

5
still in the loop
4
still in the loop
3
still in the loop
2
still in the loop
1
still in the loop
0

13.4 until 命令

until test_command
do
	commands
done

與 while 命令相似,until 命令也能夠定義多個測試命令,但只有最後一個命令的退出狀態碼起做用。

13.7 控制循環

break 命令

break n,默認狀況下,n 爲1,表示當即結束當前層的循環;若 n 爲2,則該命令會中止下一級的外部循環。

continue 命令

continue n,n 默認爲1,表示跳過當前循環中剩餘的命令。

13.8 處理循環的輸出

能夠對循環的輸出使用管道或進行重定向,只需在 done 命令後添加一個處理命令便可。

14.1 命令行參數

位置參數 ( positional parameter )

bash shell 將被稱爲位置參數的特殊變量對應分配給輸入到命令行中的全部參數,包括 shell 所執行的腳本名稱。位置參數變量是標準的數字,其中 $0 是腳本程序名,$1 對應第一個參數,以此類推,直到第九個參數 $9,對於這以後更多的參數,須要在變量數字周圍加上花括號,如第十一個參數對應 ${11}

獲取腳本名

$0 變量所保存的一般是帶有路徑的腳本名,而非 basename 。不過,使用外部命令 basename 便可提取出想要的不帶路徑的腳本名。

14.2 特殊參數變量

參數統計

特殊變量 $# 統計了命令行中輸入的參數的個數(注意,這不包含程序名,不要混淆了),能夠經過該變量直接獲取最後一個參數,不過應寫成這種形式:${!#}(注意,當沒有輸入任何參數時,獲取到的是程序名,而不是參數)。

下面的腳本將遍歷並輸出全部參數,其採用了 C 語言風格的 for 命令,並使用了嵌套的參數替換:

#!/bin/bash
for (( i = 1; i <= $#; i++ ))
do
	echo "param: ${!i}"
done
一次獲取全部的參數

特殊變量 $* 將全部參數做爲一個總體以當成一個單詞來保存;而特殊變量 $@ 將全部參數看成同一字符串中的多個獨立的單詞來保存,從而使得能夠直接經過 for 命令來遍歷這些參數。

14.4 處理選項

shift 命令

默認狀況下,該命令會將除 $0 外的每一個位置參數變量向左移動一個位置,其中變量 $1 的值將在每次移動中被遺棄,取而代之是 $2 的值(若是有的話)。能夠給該命令提供一個參數,指明每次要移動的距離。

注意,該命令同時也影響着特殊變量 $#, $* 以及 $@ 的值。

選項與參數

選項 是跟在單破折線(-)後的單個字母,對於 shell 腳本的內部處理來講,它是特殊的參數,而 參數 對應着普通的參數。

當同時使用選項與普通參數時,標準的處理方式是使用特殊字符將二者分開,該字符會告訴腳本什麼時候結束選項以及普通參數什麼時候開始。對 Linux 來講,這個特殊字符是雙破折線(--)。

選項的特殊狀況
  • 帶值的選項:有些選項會帶上一個額外的參數值,如 -m value
  • 合併的選項:將多個選項放到了同一個參數之中,如 -ac
  • 選項與值合寫:如 -mValue
getopt 命令

用戶在命令行中每每採用更爲溫馨的輸入習慣來輸入選項及普通參數,這給腳本對選項的處理帶來了難度。經過使用 getopt 命令,可將習慣性的輸入格式化爲標準的腳本選項及普通參數輸入。

getopt optstring parameters

optstring 定義了命令行中有效的選項字母,並定義了哪些選項須要參數值。首先列出全部有效的選項字母,然後在帶有參數值的選項字母后加上一個冒號。該命令會基於 optstring 來解析 parameters ,並返回其標準形式。

$ getopt ab:cd -acb val1 -d val2 val3
-a -c -b val1 -d -- val2 val3

在腳本中使用該命令時,每每搭配 set 命令來將腳本的命令行參數替換爲其標準形式,只須要在腳本開始處添加這樣一條語句:set -- $(getopt -qu optstring "$@") ,其中 -q 是 getopt 命令的選項,表示忽略錯誤消息,好比發現了無效的選項;而 -- 是 set 命令的一個選項(真是很不標準的一個選項),使得 set 將其所在腳本對應的命令行參數替換爲該選項的參數值。

-q 選項致使的錯誤

當 getopt 命令僅帶上 -q 選項後,除了會忽略錯誤消息外,其輸出還會發生微小的變化:

$ getopt -q ab:cd -acb val1 -d val2 val3
-a -c -b 'val1' -d -- 'val2' 'val3'

當腳本想要輸出 val2 時,實際上會輸出 'val2' (雖然 echo 'hello' 僅會打印出 hello)。若是不想要這種輸出,能夠再添加一個 -u 選項來避免輸出被引號包圍,事實上,-u 選項每每是必要的,由於在數值比較中,i<10 是有效的,而 i<'10' 顯然是無效的。

此外,也能夠不帶任何選項,而只需將標準錯誤重定向到 /dev/null 便可。

實例

下面的腳本帶有三個選項,-d 要求打印出分界符,-u 及其參數值表示一個單元的內容,-n 及其參數值決定要將該單元打印多少次,默認爲 1 。

#!/bin/bash

set -- $(getopt -qu u:n:d "$@")

num=1
delimit=no

while [ -n "$1" ]
do
	case "$1" in
		-u)
			unit=$2
			shift	;;
		-n)
			num=$2
			shift	;;
		-d)
			delimit=yes	;;
		--)
			shift
			break	;;
		*)
			echo "some error"
			exit 1	;;
	esac
	shift
done

if [ $delimit = yes ]; then
	echo "->"
fi
if [ -z "$unit" ]; then
	echo "error: need unit"
else
	for (( i=0; i<$num; i++ ))
	do
		echo -n "$unit"
	done
	echo
fi
if [ $delimit = yes ]; then
	echo "<-"
fi

if [ $# -gt 0 ]; then
	echo "[$#]remained param: $*"
fi
$ ./necho.sh -du\& -n5 "hello world"
->
&&&&&
<-
[2]remained param: hello world
$ getopt -qu u:n:d -du"a b" -n5 "hello world"
 -d -u a b -n 5 -- hello world

可見,getopt 命令能夠將合併選項拆開,也能夠處理選項與其參數值間沒有空格的狀況,可是,它卻沒法把 hello worlda b 當成是一個參數來處理。

更高級的 getopts 命令

getopts optstring variable

optstring 與 getopt 的相似,首先列出有效的選項字母,然後在須要的參數值的選項後加上冒號。不像 getopt ,getopts 命令一次只處理一個選項,當處理完全部選項後,它會返回一個大於0的退出狀態碼。

getopts 命令將當前選項保存在 variable 中(不帶單破折線),若是該選項有參數值的話,則將參數值保存在 OPTARG 中。此外,環境變量 OPTIND 始終保存着下一個要解析的命令行參數的位置,位置索引從1開始,所以,當解析完畢後,只須要 shift $[ $OPTIND - 1 ] 便可方便地處理餘下的普通參數。

能夠在 optstring 以前加上一冒號,這會使得 getopts 在發現錯誤時保持靜默,但這並不意味着它不會處理錯誤。

  • 當發現無效選項時,variable 被設置爲 ?,而選項字母被存放在 OPTARG 中;
  • 當選項缺乏要求的參數值時,variable 被設置爲 :,同時該選項字母會被存放在 OPTARG 中。

若 optstring 不以冒號開始,

  • 當發現無效選項或選項缺乏要求的參數值時,variable 被設置爲 ?,且 OPTARG 會被 unset ,此外還會打印出一條診斷消息。

用 getopts 重寫上面實例的選項處理部分:

#!/bin/bash

num=1
delimit=no

while getopts :u:n:d opt
do
	case "$opt" in
		u) unit=$OPTARG	;;
		n) num=$OPTARG	;;
		d) delimit=yes	;;
		:) echo "Required argument for $OPTARG is not found."
			exit 1	;;
		*) echo "[$opt] Unknown option: $OPTARG"	;;
	esac
done

shift $[ $OPTIND - 1 ]

# - - -
$ ./necho.sh -hdu"a b" -n5 "hello world"
[?] Unknown option: h
->
a ba ba ba ba b
<-
[1]remained param: hello world

腳本正確地識別出選項 -u 的參數爲 a b,這是由於 getopts 正確地認爲其是一個參數。此外,hello world被認爲是一個參數,這是由於對於命令行參數來講,確是如此。

但 getopts 命令並不總能識別出缺乏參數的選項:

$ ./necho.sh -du
Required argument for u is not found.
$ ./necho.sh -u -d
-d

文件描述符與重定向

重定向符號

[fd]{<|>|<>}{[ ]file|&[ ]fd}

[src]{<|>|<>}{dst},若沒有給出 src 部分,默認 >1><0<

永久重定向

默認狀況下,重定向都是臨時的,能夠經過 exec 命令在腳本執行期間永久重定向,例如

#!/bin/bash

echo "[1]This output should be shown at screen"
tmpfile=$( mktemp out.XXXXXX )
echo "[1]This output should go to the $tmpfile file" >> $tmpfile
echo "[2]This output should be shown at screen"
exec 3>& 1
exec >> $tmpfile
echo "[2]This output should go to the $tmpfile file"
exec >&3
echo "[3]This output should be shown at screen"
exec <$tmpfile
echo "The content of the $tmpfile file:"
while read
do
	echo $REPLY
done
echo "That's over"
rm $tmpfile
$ ./test.sh
[1]This output should be shown at screen
[2]This output should be shown at screen
[3]This output should be shown at screen
The content of the out.rmmGdo file:
[1]This output should go to the out.rmmGdo file
[2]This output should go to the out.rmmGdo file
That's over
關閉文件描述符

要關閉文件描述符,將其重定向到特殊文件描述符 - 便可。

17 函數

17.1 建立與使用函數

建立函數
function name {
	commands
}
name() {
	commands
}

函數名是惟一的,若是後定義的函數使用了重複的函數名,以前的函數將會被覆蓋,且這一切不會有任何提示。

使用函數

像使用通常的命令同樣,直接鍵入函數名便可調用該函數。且函數在使用前應該被定義,不然將產生錯誤消息。

17.2 返回值

使用退出狀態碼

能夠將函數的退出狀態碼當成是返回值,默認狀況下,函數的退出狀態碼就是函數中最後一條命令的退出狀態碼。經過在函數中使用 return 命令,能夠退出函數並指定一個整數值做爲退出狀態碼。

因爲退出狀態碼的取值爲 0~255,因此 return 一個非法的值時,將產生一個錯誤值(這並不會產生任何錯誤消息)。

使用標準輸出

經過命令替換能夠將函數的標準輸出賦給變量,這是一種獲取函數返回值的更好的方法。

17.3 函數中的變量

傳遞參數

bash shell 將函數看成是小型的腳原本對待,這意味着能夠像普通腳本那樣向函數傳遞參數。

變量的做用域

默認狀況下,在腳本中任何位置定義的任何變量都是全局變量。但對於函數中的變量來講,狀況有些特殊。

  • 當函數經過命令替換執行時,因爲這一過程建立了子 shell 來執行函數,因此雖然函數仍然能夠經過繼承來獲取到父 shell 中的變量的值,但函數的執行將不會影響到全局環境,這包括對全局變量值的更改以及定義新的變量。
  • 當函數以正常方式執行時,則會影響到所有環境。
局部變量

local locVar or local locVar=expr

注意,與初始化全局變量不一樣,使用 local 聲明或定義局部變量的操做將更新特殊變量 $?,這是由於 local 是一個內建命令,而命令具備退出狀態碼。

若是想經過命令替換給一個局部變量賦值,同時獲取到該命令的退出狀態碼的話,應該使用以下方式:

local locVar
locVar=`command(s)`
exitStatus=$?
實例 —— 階乘運算
#!/bin/bash

factorial() {
	if [ $1 -eq 1 ]; then
		echo 1
	else
		local ret=` factorial $[ $1 - 1 ]`
		echo $[ $ret * $1 ]
	fi
}

read -p "Input a number: " num
ret=` factorial $num `
echo "Result: $ret"
exit 0

使用庫

能夠建立一個只包含有函數的公用庫文件,然後在多個腳本中引用該文件。

source 命令

該命令會在當前 shell 上下文中執行命令,而不是建立一個新的 shell。能夠經過該命令在腳本中運行庫文件,這樣就可使用庫中的函數了。

source 命令有一個別名,即 點操做符 ( dot operator ),寫做 .

18.1 文本菜單

select 命令
select variable in list
do
	commands
done

list 是由空格分隔的文本選項列表,select 將每一個列表項顯示成一個帶編號的選項,然後循環進行如下步驟:

  • 打印出由 PS3 環境變量定義的提示符並等待用戶輸入;
  • 獲取輸入並將與輸入的編號對應的文本選項存入到 variable 中(若輸入無效,則 [ -z $variable ] 將爲真);
  • 執行 commands,若是遇到 break ,則退出循環。
實例
#!/bin/bash

PS3="Enter your chioce: "

clear

select option in	\
	"Exit program"	\
	"Display memory usage"	\
	"Display disk space"	\
	"Display date and time"
do
	case $option in
		"Exit program")	break	;;
		"Display memory usage")	free -h	;;
		"Display disk space")	df -h	;;
		"Display date and time")	date	;;
		*)	echo "Sorry, wrong selection"	;;
	esac
done

clear
exit 0

18.2 TUI

用於 C 語言

爲了繪製 TUI (Text-based User Interface ),可使用 ncurcesnewt 庫,只是注意在使用前確認一下是否安裝了其 -dev 包便可。

適合 shell 腳本的

使用 dialogwhiptail 命令,便可在終端中繪製各類窗口來製做 TUI 。在 apt 中,對 whiptail 包的描述爲:

Displays user-friendly dialog boxes from shell scripts

Whiptail is a "dialog" replacement using newt instead of ncurses. It provides a method of displaying several different types of dialog boxes from shell scripts. This allows a developer of a script to interact with the user in a much friendlier manner.

whiptail
選項的類別

whiptail 命令的選項分爲兩類:option、box-option 。其中,後者決定了要繪製的窗口及其內容,而前者用於調整後者的一些細節。

哪個按鈕

該命令經過退出狀態碼來告知腳本是哪個按鈕被選擇了,當選擇 yes-buttonok-button 時,返回 0;而當選擇 no-buttoncancel-button 時,返回 1(當有錯誤發生或按下 ESC 時,退出狀態碼將是 255)。

獲取輸入

inputbox 會將用戶輸入的文本輸出到 STDERR 中,一樣,menu 也會將用戶所選項的 tag 文本輸出到 STDERR 中,這使得沒法經過命令替換來將用戶的輸入或菜單選擇存儲到變量中。解決這一問題的其中一個方法是,臨時交換 STDERR 與 STDIN 。

實例

下面的實例在上面文本菜單的基礎上,提供了 TUI 。

#!/bin/bash

esCheck() {
    if [ $? -ne 0 ]; then
        echo "exit from $1." >&2
        exit $2
    fi
}

# inputUser validUser
inputUser() {
    local user
    user=`whiptail --inputbox "Input your user name please." 8 40 "w" 3>&1 1>&2 2>&3`
    esCheck "inputbox" 1
    if [ "$user" != $1 ]; then
		echo "Illegal user" >&2
		exit 1
    fi
    return 0
}

# inputPassword chanceTimes correctPassword
inputPassword() {
    local password
    local chanceTimes=$[ $1 - 1 ]
    while [ $chanceTimes -ge 0 ]
    do
        password=`whiptail \
            --title "password dialog" \
            --passwordbox "Input your secret password." 8 60 \
            3>&1 1>&2 2>&3`
        esCheck "passwordbox" 1
	if [ "$password" = $2 ]; then
	    return 0
	else
	    if [ $chanceTimes = 0 ]; then
		echo "No chance for password inputing." >&2
		exit 1
	    fi
	    whiptail \
		--title "remained chances: $chanceTimes" \
		--msgbox "The password is wrong, try it again." 8 40
	    esCheck "msgbox" 1
	fi
		chanceTimes=$[ $chanceTimes - 1 ]
    done
}

# menu :opt
menu() {
    local opt
    local es
    opt=`whiptail \
	--title "menu dialog" \
	--menu "Select your chioce" \
	25 60 20 \
	"e" "Exit program" \
	1   "Display memory usage" \
	2   "Display disk space" \
	3   "Display date and time" \
	3>&1 1>&2 2>&3`
    es=$?
    echo $opt
    return $es
}

inputUser $USER
inputPassword 3 "123456"
temp=` mktemp tmp.XXXXXX `
while :
do
    opt=`menu`
    esCheck "menu" 1
    case $opt in
	"e" )	break	;;
	1   )	free -h > $temp
    		whiptail --title "Memory usage" --textbox $temp 25 90 --scrolltext	;;
	2   )	df -h > $temp
			whiptail --title "Disk space" --textbox $temp 25 60 --scrolltext	;;
	3   )	whiptail --title "Date and time" --msgbox "`date`" 8 60	;;
	*   )	echo "This message shouldn't be shown!" >&2; exit 1	;;
    esac
done
rm $temp
for i in `seq 100`
do
    sleep 0.1
    echo $i
done | whiptail --gauge "Please wait for the program to exit..." 8 60 0
exit 0
相關文章
相關標籤/搜索