Command Grouping 主要有兩種形式:linux
(command;command[;command;command...])
{ command;[command;command;...] }
;
兩種命令分組的區別在於,使用進程列表將會建立出一個子shell來執行分組中的命令,而另外一種形式則不會如此。shell
coproc 是一個 shell 關鍵字,其用法爲 coproc [name] command
,它將在後臺生成一個子 shell,並在其中執行 command 。協程的名字默認爲 COPROC,當顯式命名時,command 部分應採用命令分組的形式(注意若使用進程列表,將產生嵌套的子 shell )。express
爲了將命令的輸出提取出來以賦給變量,可使用以下兩種形式執行命令:編程
下面的腳本將當前日期及時間嵌入到特定字串中輸出:bash
#!/bin/bash my_var=`date` # or my_var=$(date) echo "The date and time are: " $my_var
Bourne shell 提供了一個特別的命令,即expr,來處理數學表達式,該命令可以識別少數的數字及字符串操做符,此外該命令對數字的支持僅限於整數。而且,應注意到,在shell中使用時還須要對一些操做符進行轉義。ide
在 bash shell 中,爲了不轉義操做符帶來的麻煩,應採用這種形式 $[ operation ]
來執行數學表達式。函數
z shell 提供了完整的浮點數算術支持。而在bash中,爲了避開數學運算的整數限制,常見的方案是使用內建的bash計算器,即bc 。oop
下面是一個在腳本中使用bc的簡單例子:測試
#!/bin/bash res=$( echo "scale=2; var1=3.1415; var1 / 2" | bc ) echo "Result is $res"
bc有一個內建變量 scale ,默認值爲0。該變量控制着在含有除法的浮點數運算中,結果所保留的小數位數。浮點乘法不受其影響。ui
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 ltstr1 op str2
, op: = != < >-op str1
, op: n z-op file
, op: d e f r s w x O Gfile1 -op file2
, op: nt otif-then 語句容許使用布爾邏輯來組合測試,有兩種布爾運算符可用:
[ condition1 ] op [ condition2 ]
, op: && ||
若是測試中的操做數包含了變量,那麼建議使用引號將變量替換表達式引住。這是由於,若是變量爲空(這在使用命令替換給變量賦值時可能會發生),那麼變量替換將使得某些測試缺乏操做數,從而形成錯誤;或者若是變量的值中包含空格,那麼對於測試,將會出現非法或多餘的參數。
經過使用 [ "$var" op val ]
這種形式,能夠明確告知 test 命令,那裏有一個參數,即使參數是空值或包含空格。
(( expression ))
,其中可以使用各類各樣的數學運算符,且不須要對諸如 < > 這樣的操做符進行轉義。
此外,因爲 expression 能夠是任意的數學賦值或比較表達式,故也可將雙括號命令用做給變量賦值的普通命令。
[[ expression ]]
,expression 使用了 test 命令中採用的標準字符串比較,同時提供了額外的特性:模式匹配,經過使用雙等號操做符來使用這一特性。
case variable in pattern1 | pattern2) commands1;; pattern3) commands2;; *) default_commands;; esac
shell 中的 case 不須要 "break" 語句便可跳轉出去。
IFS 環境變量定義了 bash shell 用做字段分隔符的一系列字符。默認的字段分隔符有:
for var in list do commands done
for var in list; do commands done
for (( variable assignment ; condition ; iteration process ))
注意,有些部分沒有遵循 bash shell 的標準:
下面是一個採用該風格但沒有實際意義的例子:
for (( a = 1, b = 10; a<=10 && b > 6; a++, b-- )) do echo "$a - $b" done
並非全部的 shell 都像 bash 同樣支持 C 語言風格的 for 命令,如此,就須要寫出一大串數字序列做爲 list ,不過,幸虧有 seq 命令能夠幫助咱們生成數字序列。
seq [option]... [first] [increment] last
默認 first 和 increment 爲 1 。
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
until test_command do commands done
與 while 命令相似,until 命令也能夠定義多個測試命令,但只有最後一個命令的退出狀態碼起做用。
break n
,默認狀況下,n 爲1,表示當即結束當前層的循環;若 n 爲2,則該命令會中止下一級的外部循環。
continue n
,n 默認爲1,表示跳過當前循環中剩餘的命令。
能夠對循環的輸出使用管道或進行重定向,只需在 done 命令後添加一個處理命令便可。
bash shell 將被稱爲位置參數的特殊變量對應分配給輸入到命令行中的全部參數,包括 shell 所執行的腳本名稱。位置參數變量是標準的數字,其中 $0
是腳本程序名,$1
對應第一個參數,以此類推,直到第九個參數 $9
,對於這以後更多的參數,須要在變量數字周圍加上花括號,如第十一個參數對應 ${11}
。
$0
變量所保存的一般是帶有路徑的腳本名,而非 basename 。不過,使用外部命令 basename 便可提取出想要的不帶路徑的腳本名。
特殊變量 $#
統計了命令行中輸入的參數的個數(注意,這不包含程序名,不要混淆了),能夠經過該變量直接獲取最後一個參數,不過應寫成這種形式:${!#}
(注意,當沒有輸入任何參數時,獲取到的是程序名,而不是參數)。
下面的腳本將遍歷並輸出全部參數,其採用了 C 語言風格的 for 命令,並使用了嵌套的參數替換:
#!/bin/bash for (( i = 1; i <= $#; i++ )) do echo "param: ${!i}" done
特殊變量 $*
將全部參數做爲一個總體以當成一個單詞來保存;而特殊變量 $@
將全部參數看成同一字符串中的多個獨立的單詞來保存,從而使得能夠直接經過 for 命令來遍歷這些參數。
默認狀況下,該命令會將除 $0
外的每一個位置參數變量向左移動一個位置,其中變量 $1
的值將在每次移動中被遺棄,取而代之是 $2
的值(若是有的話)。能夠給該命令提供一個參數,指明每次要移動的距離。
注意,該命令同時也影響着特殊變量 $#
, $*
以及 $@
的值。
選項 是跟在單破折線(-
)後的單個字母,對於 shell 腳本的內部處理來講,它是特殊的參數,而 參數 對應着普通的參數。
當同時使用選項與普通參數時,標準的處理方式是使用特殊字符將二者分開,該字符會告訴腳本什麼時候結束選項以及普通參數什麼時候開始。對 Linux 來講,這個特殊字符是雙破折線(--
)。
-m value
;-ac
;-mValue
。用戶在命令行中每每採用更爲溫馨的輸入習慣來輸入選項及普通參數,這給腳本對選項的處理帶來了難度。經過使用 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 world
或 a b
當成是一個參數來處理。
getopts optstring variable
optstring 與 getopt 的相似,首先列出有效的選項字母,然後在須要的參數值的選項後加上冒號。不像 getopt ,getopts 命令一次只處理一個選項,當處理完全部選項後,它會返回一個大於0的退出狀態碼。
getopts 命令將當前選項保存在 variable 中(不帶單破折線),若是該選項有參數值的話,則將參數值保存在 OPTARG 中。此外,環境變量 OPTIND 始終保存着下一個要解析的命令行參數的位置,位置索引從1開始,所以,當解析完畢後,只須要 shift $[ $OPTIND - 1 ]
便可方便地處理餘下的普通參數。
能夠在 optstring 以前加上一冒號,這會使得 getopts 在發現錯誤時保持靜默,但這並不意味着它不會處理錯誤。
?
,而選項字母被存放在 OPTARG 中;:
,同時該選項字母會被存放在 OPTARG 中。若 optstring 不以冒號開始,
?
,且 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
要關閉文件描述符,將其重定向到特殊文件描述符 -
便可。
function name { commands }
name() { commands }
函數名是惟一的,若是後定義的函數使用了重複的函數名,以前的函數將會被覆蓋,且這一切不會有任何提示。
像使用通常的命令同樣,直接鍵入函數名便可調用該函數。且函數在使用前應該被定義,不然將產生錯誤消息。
能夠將函數的退出狀態碼當成是返回值,默認狀況下,函數的退出狀態碼就是函數中最後一條命令的退出狀態碼。經過在函數中使用 return 命令,能夠退出函數並指定一個整數值做爲退出狀態碼。
因爲退出狀態碼的取值爲 0~255,因此 return 一個非法的值時,將產生一個錯誤值(這並不會產生任何錯誤消息)。
經過命令替換能夠將函數的標準輸出賦給變量,這是一種獲取函數返回值的更好的方法。
bash 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
能夠建立一個只包含有函數的公用庫文件,然後在多個腳本中引用該文件。
該命令會在當前 shell 上下文中執行命令,而不是建立一個新的 shell。能夠經過該命令在腳本中運行庫文件,這樣就可使用庫中的函數了。
source 命令有一個別名,即 點操做符 ( dot operator ),寫做 .
。
select variable in list do commands done
list
是由空格分隔的文本選項列表,select 將每一個列表項顯示成一個帶編號的選項,然後循環進行如下步驟:
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
爲了繪製 TUI (Text-based User Interface ),可使用 ncurces
或 newt
庫,只是注意在使用前確認一下是否安裝了其 -dev
包便可。
使用 dialog
或 whiptail
命令,便可在終端中繪製各類窗口來製做 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 命令的選項分爲兩類:option、box-option 。其中,後者決定了要繪製的窗口及其內容,而前者用於調整後者的一些細節。
該命令經過退出狀態碼來告知腳本是哪個按鈕被選擇了,當選擇 yes-button
或 ok-button
時,返回 0;而當選擇 no-button
或 cancel-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