一、 客戶端輸入
腳本執行的過程是對數據變量進行處理的過程,之前在腳本中處理的數據都是靜態數據,而不是和客戶端交互的動態數據。在大多數情況下腳本執行過程需要和客戶端進行交互,用來獲得腳本處理的數據,這些數據包括參數和命令行選項等等。下面的內容是採用不同的方式從客戶端獲取數據。
12.1 命令行參數
向腳本傳遞參數的基本方式是命令行參數,命令行參數是在運行命令時同時傳遞的參數,如下方式:
bash67.sh 60 2 |
上面的命令將60和2兩個參數傳遞個腳本bash67.sh,在腳本運行過程中通Shell內置的變量可以取得這些命令行參數值,下面通過例子說明。
12.1.1 讀取命令行參數
在腳本中有一些特殊的內置變量叫做位置參數,當腳本執行時,會將這些特殊的變量分配給從命令行輸入的所有參數,這些參數包括腳本的名稱,變量的名稱從0開始,每個變量前使用$符號,如:$0變量代表腳本的名稱,$1變量代表命令行第一個參數,以此類推,$2變量代表第二個參數,$9代表第九個參數,從第十個參數開始採用大括號的方式,${10}代表第十個參數。下面通過例子說明。
例子:
#!/bin/bash # 將命令行參數作爲循環的次數 for (( i = 1 ; i <= $1 ; i++ )) do echo "Loop $i" done |
控制檯顯示:
$ bash66.sh 5 Loop 1 Loop 2 Loop 3 Loop 4 Loop 5 |
上面的腳本簡單的使用的命令行參數,參數決定了腳本循環的次數,因爲就一個參數,對參數的引用使用$1,Shell腳本會將命令行參數自動分配給$1。
在腳本中可以像普通變量一樣使用位置變量$1、$2和$0。Shell腳本會自動將命令行參數的值分配給位置變量,如果命令行參數有多個,需要使用空格將參數分開。
例子:
#!/bin/bash # 第一個位置參數,代表執行的腳本,包括目錄 echo "\$0: $0" # 位於腳本後面的第一個參數 echo "\$1: $1" # 位於腳本後面的第二個參數 echo "\$2: $2" # 成績 var=$[ $1 * $2 ] echo "$1 and $2 product is $var" |
控制檯顯示:
$ bash67.sh 60 2 $0: ./bash66.sh $1: 60 $2: 2 60 and 2 product is 120 |
命令行參數也可以是文本字符串。
例:bash68.sh:
#!/bin/bash # 判斷命令行參數值是否是yarn if [ "$1" = "yarn" ] then echo "User is yarn!" # 判斷命令行參數值是否是root elif [ "$1" = "root" ] then echo "User is root!" else echo "User is other!" fi |
控制檯顯示:
$ bash68.sh root User is root! $ bash68.sh yarn User is yarn! $ bash68.sh hadoop User is other! |
如果命令行參數是多個文本字符串,單個參數中間存在空格,Shell會將這個參數分配成兩個變量,如下例子。
例子:
#!/bin/bash # 如果命令行文本參數中存在空格,Shell會分配參數值到多個變量 echo "$1" echo "$2" echo "$3" |
控制檯顯示:
$ bash69.sh Hello Bash Shell Hello Bash Shell |
解決的辦法是對存在空格的參數使用雙引號或單引號,如下:
控制檯顯示:
$ bash69.sh "Hello Bash Shell" Hello Bash Shell $ bash69.sh 'Hello Bash Shell' Hello Bash Shell |
如果命令行參數超過九個,從第十個參數開始引用的方式是使用大括號,如第十一各參數採用${11},第十二個參數${12},以此類推,下面通過例子說明。
例子:
#!/bin/bash # 從1到9採用的方式 echo "\$1: $1" echo "\$2: $2" echo "\$3: $3" echo "\$4: $4" echo "\$5: $5" echo "\$6: $6" echo "\$7: $7" echo "\$8: $8" echo "\$9: $9" # 從10開始採用大括號的方式 echo "\${10}: ${10}" echo "\${11}: ${11}" echo "\${12}: ${12}" |
控制檯顯示:
$ bash70.sh 1 2 3 4 5 6 7 8 9 10 11 12 $1: 1 $2: 2 $3: 3 $4: 4 $5: 5 $6: 6 $7: 7 $8: 8 $9: 9 ${10}: 10 ${11}: 11 ${12}: 12 |
12.1.2 從命令行參數讀取腳本名稱
Shell會將命令行的參數自動分配給特殊的位置變量,從$1開始。但是腳本的名稱會自動分配給$0
例子:
#!/bin/bash # 將腳本名稱保存到變量 filename=$0 # 打印 echo "Shell script name is $filename" |
控制檯顯示:
$ bash71.sh Shell script name is ./bash71.sh $ /home/yarn/bash01/bash71.sh Shell script name is /home/yarn/bash01/bash71.sh |
如果在腳本所在的目錄執行腳本,顯示的腳本名稱爲:./bash71.sh,如果採用完整的腳本名稱執行腳本,顯示的腳本名稱:/home/yarn/bash01/bash71.sh。變量$0保存的腳本名稱包括目錄名稱。在有些情況下我們只需要知道腳本名稱而不需要包括目錄名。可以通過命令basename來獲取腳本名稱而不包括路徑,通過一個例子說明。
例子:
#!/bin/bash # 通過basename命令取得不帶路徑的腳本名稱 scriptname=`basename $0`
echo "Bash Shell script name is $scriptname" |
控制檯顯示:
$ bash72.sh Bash Shell script name is bash72.sh $ /home/yarn/bash01/bash72.sh Bash Shell script name is bash72.sh |
另外幾個常用的命令:which、dirname和pwd命令,which命令返回完整的腳本名稱,包括腳本名稱和路徑名。dirname命令返回執行腳本的位置或目錄。pwd命令返回當前目錄完整名稱,下面通過例子說明。
例子:
#!/bin/bash # 返回完整的腳本名稱 filename=`which $0` # 返回腳本文件所在的目錄 filedir=`dirname $0` # 返回當前目錄名稱 dir=`cd $filedir ; pwd`
echo "filename: $filename" echo "filedir: $filedir" echo "dir: $dir" |
在腳本所在的目錄執行腳本。
控制檯顯示:
$ bash73.sh filename: /home/yarn/bash01/bash73.sh filedir: . dir: /home/yarn/bash01 |
在其他的目錄執行腳本。
控制檯顯示:
$ /home/yarn/bash01/bash73.sh filename: /home/yarn/bash01/bash73.sh filedir: /home/yarn/bash01 dir: /home/yarn/bash01 |
12.1.3 在腳本中測試命令行參數
在編寫帶命令行參數的腳本時要注意,如果運行腳本的用戶沒有提供參數,腳本在執行過程中會提示錯誤,這樣的腳本是存在問題的。好的編程方式是在需要使用命令行參數的位置首先判斷參數是否存在或是否爲空,如果參數不存在要給出提示信息並終止程序的執行。下面通過例子說明。
例子:
#!/bin/bash # 判斷命令行參數是否存在 if [ ! -z "$1" ] && [ ! -z "$2" ] then # 如果存在,相乘 product=$[ $1 * $2 ] else # 提示信息 echo "Missing parameter!" # 終止腳本執行 exit 1 fi # 如果執行了exit命令,後面的命令不會執行 echo "The product of $1 and $2 is $product" |
控制檯顯示:
$ bash74.sh 3 4 The product of 3 and 4 is 12 $ bash74.sh Missing parameter! |
12.2 系統參數變量
Shell提供了一些特殊的參數變量,他們會保存命令行參數相關的信息,在腳本中會用到這些變量。下面通過例子說明每個變量的使用方式。
12.2.1 參數計算
如果命令行參數個數較少,可以採用上面例子的方式對每一個參數進行測試,如果命令行參數個數有多個,那麼逐個測試參數就比較繁瑣了。Shell提供了另外一個內置變量$#,用來保存命令行參數的個數,注意:$0參數不包含在內。可以通過這個變量判斷參數的個數是否符合要求。$#變量可以向普通變量一樣在腳本中引用。下面通過一個簡單的例子說明$#變量的使用方式。
例子:
#!/bin/bash # 將參數個數賦值給變量 sum=$# echo "Parameter number is $sum" |
控制檯顯示:
$ bash75.sh a b c d e f g Parameter number is 7 $ bash75.sh 1 2 3 Parameter number is 3 $ bash75.sh Parameter number is 0 |
修改上面的例子,通過判斷參數的個數來決定腳本是否執行
例子:
#!/bin/bash # 判斷命令行參數個數是否等於2 if [ $# -eq 2 ] then product=$[ $1 * $2 ] # 判斷命令行參數是否小於2 elif [ $# -lt 2 ] then echo "Missing parameter!" # 終止腳本執行 exit 1 # 判斷命令行參數個數是否大於2 elif [ $# -gt 2 ] then echo "Parameter number error!" # 終止腳本執行 exit 1 fi
echo "The product of $1 and $2 is $product" |
控制檯顯示:
$ bash76.sh 12 3 22 Parameter number error! $ bash76.sh Missing parameter! $ bash76.sh 12 3 The product of 12 and 3 is 36 |
另外,變量$#還有一個特殊的用途,因爲$#變量保存的是參數的個數,理論上可以通過${$#}來訪問最後一個變量,而不需要知道參數的數量。但是Bash Shell腳本中的大括號內不能使用$符,所以使用!號代替大括號內的$符。下面通過例子說明。
例:bash77.sh:
#!/bin/bash # 大括號中不能使用$符,所以輸出的結果錯誤 echo "${$#}" # 使用!號替代$符 echo "${!#}" |
控制檯顯示:
$ bash77.sh a b c d 5897 d $ bash77.sh 23 5898 23 $ bash77.sh 5900 ./bash77.sh |
可以看出${$#}的輸出不正確,而${!#}的輸出正確,輸出的是最後一個參數值。但是需要注意的是當腳本沒有命令行參數時,${!#}輸出的是帶目錄的腳本名稱。
12.2.2 取得所有的參數
對於命令行參數還可以採用其他的方式訪問,一次性全部讀取然後循環遍歷。Shell提供了兩個特殊變量$*和$@對命令行參數快速訪問。Shell會將所有的命令行參數分配給這兩個變量,通過這兩個變量都可以循環訪問命令行所有的參數。那麼這兩個變量有什麼區別呢!$*變量將命令行提供的所有參數當成一個完整的單詞保存,這個完整的單詞包含所有的命令行參數,$*變量會將這個完整的單詞當做一個對象或一個參數而不是多個對象或單詞。不同的是,$@變量會將命令行提供的所有參數當成同一個字符串中的多個獨立的單詞,允許使用for循環對每個參數進行遍歷。下面通過幾個例子說明變量$*和$@區別是使用方式。
第一個例子只是簡單的將兩個變量的值輸出到控制檯,通過例子可以看到兩個變量都將命令行的所有參數都保存到了變量中。
例子:
#!/bin/bash # 輸出變量$*到控制檯 echo "\$*: $*" # 輸出變量$@到控制檯 echo "\$@: $@" |
控制檯顯示:
$ bash78.sh a b c d e f g $*: a b c d e f g $@: a b c d e f g |
從這個例子中好像看不到兩個參數的區別,都將命令行所有的參數都保存到了變量中。下面的例子採用for循環遍歷兩個變量的參數,注意:在for循環中沒有使用雙引號,注意有什麼區別。
例:bash79.sh:
#!/bin/bash # 計數器 count=1 # 循環遍歷變量$* for var1 in $* do echo "\$* $count: $var1" # 計數器加一 count=$[$count + 1 ] done # 計數器復位 count=1 # 循環遍歷變量$@ for var2 in $@ do echo "\$@ $count: $var2" # 計數器加一 count=$[ $count + 1 ] done |
控制檯顯示:
$ bash79.sh a b c d e f g $* 1: a $* 2: b $* 3: c $* 4: d $* 5: e $* 6: f $* 7: g $@ 1: a $@ 2: b $@ 3: c $@ 4: d $@ 5: e $@ 6: f $@ 7: g |
從上面的例子可以看出,如果兩個變量在for循環中沒有加雙引號,從輸出可以看出兩個變量沒有區別。下面的例子中在for循環中將兩個變量加上雙引號,注意輸出的變化。
例子:
#!/bin/bash # 計數器 count=1 # 循環遍歷變量$* for var1 in "$*" do echo "\$* $count: $var1" # 計數器加一 count=$[$count + 1 ] done # 計數器復位 count=1 # 循環遍歷變量$@ for var2 in "$@" do echo "\$@ $count: $var2" # 計數器加一 count=$[ $count + 1 ] done |
控制檯顯示:
$ bash80.sh a b c d e f g $* 1: a b c d e f g $@ 1: a $@ 2: b $@ 3: c $@ 4: d $@ 5: e $@ 6: f $@ 7: g |
從輸出可以看出,$*變量只循環了一次,將所有的參數當成一個完整的單詞,而$@變量循環多次,將所有的命令行參數逐個輸出到控制檯。
通過上面的例子可以看出,$*變量將所有的命令行參數當成一個參數,而$@變量會單獨處理每一個參數,通過for循環可以變量命令行參數。
12.2.3 命令行參數的移動
Shell提供了一個命令shift,這個命令可以將命令行參數向左移動,將左面的參數覆蓋。這種方式處理命令行參數較爲常用,不需要循環遍歷命令行參數。
使用shift命令時,命令行參數的個數會自動減一,參數$4的值會移動到$3,$3參數的值會移動到$2,參數$2的值移動到$1,而$1參數的值被刪除,注意:變量$0的值不會改變。這個命令的好處是在代碼中不需要循環遍歷命令行參數,並且在代碼中只需要訪問$1,每執行一次shift命令,後面的參數會填充$1變量。下面通過一個簡單的例子說明shift命令的基本用法。
例子:
#!/bin/bash # 保存參數的個數 count=$# # 循環 for (( i = 1 ; i <= $count ; i++ )) do # 輸出參數個數和變量$1的值 echo "\$#: $# \$1: $1" # 左移一位參數,變量$2的值填充變量$1,第一個參數被清除 # 命令行參數的個數減一 shift done # 輸出$0變量的值,$0變量的值不會改變 echo "\$0: $0" |
控制檯顯示:
$ bash81.sh a b c d e f g $#: 7 $1: a $#: 6 $1: b $#: 5 $1: c $#: 4 $1: d $#: 3 $1: e $#: 2 $1: f $#: 1 $1: g $0: ./bash81.sh |
命令shift執行後,參數個數減一併且變量$1的值會被後面的值依次填充。
下面的例子說明shift命令在腳本中的常用方式。
例子:
#!/bin/bash # 計數器 count=1 # 循環,判斷變量$1是否非空 while [ -n "$1" ] do echo "count: $count \$1: $1" # 計數器加一 count=$[ $count + 1 ] # 左移一位 shift done |
控制檯顯示:
$ bash82.sh a b c d e f g count: 1 $1: a count: 2 $1: b count: 3 $1: c count: 4 $1: d count: 5 $1: e count: 6 $1: f count: 7 $1: g |
注意:引用變量時最好加上雙引號。
命令shift還可以使用參數,如:shift 2。
例子:
#!/bin/bash
count=1
while [ -n "$1" ] do echo "count: $count \$1: $1" count=$[ $count + 1 ] # 左移兩位,$3的值移動到$1 shift 2 done |
控制檯顯示:
$ bash83.sh 1 2 3 4 5 6 7 8 count: 1 $1: 1 count: 2 $1: 3 count: 3 $1: 5 count: 4 $1: 7 |
shift命令後面的參數是一次移動幾位,上面的例子的參數是2,$3移動到$1,後面的參數向前移動兩位,以此類推,變量$1和變量$2的值清除。需要注意的是被清除的參數不可恢復。
12.3 讀取用戶輸入
命令行參數是和用戶端交互的一種方式,但有些情況下,腳本執行過程中需要和客戶端進行交互,如需要客戶端輸入選項,Bash Shell中提供了read命令,可以從控制檯讀取客戶端輸入。
12.3.1 從控制檯讀取輸入
read命令接受標準輸入(鍵盤)的輸入,在接受輸入後read命令會將數據放入一個變量,下面通過一個例子說明read命令的基本用法。
例子:
#!/bin/bash # 輸出提示 echo -n "Please input your name: " # 讀取輸入並保存到變量name中 read name echo -n "Please input your age: " read age echo -n "Please input your address: " read address echo -n "Please input your sex: " read sex echo -n "Please input your class: " read class
echo "-------------------" echo "Your name is $name" echo "Your sex is $sex" echo "Your age is $age" echo "Your address is $address" echo "Your class is $class" echo "-------------------" |
控制檯顯示:
$ bash84.sh Please input your name: zhangsan Please input your age: 23 Please input your address: taiyuan Please input your sex: nan Please input your class: 5 ------------------- Your name is zhangsan Your sex is nan Your age is 23 Your address is taiyuan Your class is 5 ------------------- |
echo命令的參數-n移除字符串後面的換行符。另外,read命令會將提示符後面的輸入保存爲一個變量,如下例子。
例子:
#!/bin/bash # 提示輸入 echo -n "Please input parameter: " # 將所有的輸入保存到一個變量 read parameter
echo "-------------------" echo "Parameter value is $parameter" echo "-------------------" |
控制檯顯示:
$ bash85.sh Please input parameter: Hello Bash Shell ------------------- Parameter value is Hello Bash Shell ------------------- |
read命令的另一種用法。
例:bash86.sh:
#!/bin/bash # 使用-p參數的read命令方式,變量在提示信息的後面 read -p "Please input your name: " name read -p "Please input your age: " age read -p "Please input your address: " address read -p "Please input your sex: " sex read -p "Please input your class: " class
echo "-------------------" echo "Your name is $name" echo "Your sex is $sex" echo "Your age is $age" echo "Your address is $address" echo "Your class is $class" echo "-------------------" |
控制檯顯示:
$ bash86.sh Please input your name: zhangsan Please input your age: 22 Please input your address: taiyuan Please input your sex: nan Please input your class: 5 ------------------- Your name is zhangsna Your sex is nan Your age is 22 Your address is taiyuan Your class is 5 ------------------- |
指定多個變量的read命令,在使用read命令時可以指定多個變量,將用空格分隔的值存入多個變量中,如果輸入的值的數量超過變量的數量,會將多餘的值分配給最後一個變量,通過下面的例子說明。
例:bash87.sh:
#!/bin/bash # 所有的變量在read命令提示信息的後面,使用空格分開 read -p "Please input your info -name -sex -age -address -class: " name sex age address class
echo "-------------------" echo "Your name is $name" echo "Your sex is $sex" echo "Your age is $age" echo "Your address is $address" echo "Your class is $class" echo "-------------------" |
控制檯顯示:
$ bash87.sh Please input your info -name -sex -age -address -class: zhangsan nan 23 taiyuan 5 others ------------------- Your name is zhangsan Your sex is nan Your age is 23 Your address is taiyuan Your class is 5 others ------------------- |
每個輸入的值對應一個變量,當輸入的值的數量多於變量的數量,多餘的值會保存到最後一個變量中。變量和值都是以空格分隔的。
當read命令沒有指定變量時,會將輸入的值保存到變量REPLY中,注意:如果再有輸入會將變量REPLY之前的值覆蓋,所以如果有多個輸入時可以將變量REPLY的值保存到一個用戶變量中。
例:bash88.sh:
#!/bin/bash # 提示輸入 echo -n "Please input your name: " # 如果read命令沒有指定變量名,會默認保存到REPLY變量中 read name=$REPLY echo -n "Please input your age: " read age=$REPLY echo -n "Please input your address: " read address=$REPLY echo -n "Please input your sex: " read sex=$REPLY echo -n "Please input your class: " read class=$REPLY
echo "-------------------" echo "Your name is $name" echo "Your sex is $sex" echo "Your age is $age" echo "Your address is $address" echo "Your class is $class" echo "-------------------" |
控制檯顯示:
$ bash88.sh Please input your name: zhangsan Please input your age: 23 Please input your address: taiyuan Please input your sex: nan Please input your class: 6 ------------------- Your name is zhangsan Your sex is nan Your age is 23 Your address is taiyuan Your class is 6 ------------------- |
環境變量REPLY會保存一次輸入的所有數據,如果存在其他的輸入,將覆蓋之前保存的值。
read命令會中斷腳本執行並等待用戶的輸入,如果用戶長時間沒有輸入,腳本將一直等待下去。read命令提供了參數-t,可以指定超時時間,單位是秒。如果超過了設定的時間,read命令將返回一個非0的退出碼,如果輸入沒有超時,read命令返回的退出碼爲0。可以使用if語句判斷read語句是否正確執行並給出提示信息,如下代碼。
例:bash89.sh:
#!/bin/bash # 使用if語句,如果read命令正確執行,返回的退出碼爲0,執行then後面的命令 # 如果超時,read命令返回非0的退出碼,執行else語句 if read -t 5 -p "Please input your username and password: " username password then echo "Login success : $username" else # 換行 echo echo "Login timeout..." fi |
控制檯顯示:
$ bash89.sh Please input your username and password: yarn 123 Login success : yarn $ bash89.sh Please input your username and password: Login timeout... |
read命令還可以指定輸入字符的個數,用來控制用戶的輸入,-n 3參數告訴read命令接受三個字符並保存到變量中後自動退出,不需要按回車鍵,下面通過例子說明。
例:bash90.sh:
#!/bin/bash # 指定輸入字符上限爲三個 read -n 3 -p "Please input ON/OFF: " action # 當輸入on或NO時執行then語句 if [ $action = "on" ] || [ $action = "ON" ] then echo "The system is start-up!" # 當輸入off或OFF時執行then語句 elif [ $action = "off" ] || [ $action = "OFF" ] then # 輸出換行 echo echo "The system is closed!" # 輸入其他的字符,提示輸入錯誤 else echo echo "Input error!" fi |
控制檯顯示:
$ bash90.sh Please input ON/OFF: on The system is start-up! $ bash90.sh Please input ON/OFF: off The system is closed! $ bash90.sh Please input ON/OFF: ote Input error! |
使用case語句實現上面腳本的功能。
例:bash91.sh:
#!/bin/bash
read -n 3 -p "Please input ON/OFF: " action
case $action in on | ON) echo "The system is start-up!" ;; off | OFF) echo echo "The system is closed!" ;; *) echo echo "Input error!" ;; esac |
read命令的-s參數可以使用戶的輸入隱藏,通常和用戶交互時需要用戶輸入密碼時會用到,用戶的輸入在控制檯不顯示但會保存到變量中,引用變量可以取得輸入的值。通過下面的例子說明。
例:bash92.sh:
#!/bin/bash
read -p "Please input login username: " username # 參數-s可以使輸入隱藏,通常輸入密碼時用到 read -s -p "Please input login password: " password # 判斷用戶名和密碼是否非空 if [ -n "$username" ] && [ -n "$password" ] then # 判斷用戶名和密碼是否正確 if [ $username = "yarn" ] && [ $password = "123" ] then echo echo "User: $username Login success!" else echo echo "Sorry login failed!" fi else echo echo "Please input your username and password!" fi |
使用read命令可以從Linux系統上的文件中讀取數據,使用cat命令將輸出通過管道傳遞給read命令並通過while循環將文件中的數據讀取,循環一次讀取文件中的一行數據。當文件中沒有數據時,read命令會返回一個非零的退出碼。下面通過例子說明。
例:bash93.sh:
#!/bin/bash # 保存字段分隔符默認值 IFS_bak=$IFS # 設置字段分隔符爲製表符 IFS=$'\t' # 使用cat命令輸出文件內容到read命令 cat file08 | while read line do echo "One line of data: $line" # 循環顯示一行的數據 for var in $line do echo "One column of data: $var" done done # 還原字段分隔符默認值 IFS=$IFS_bak |
控制檯顯示:
… One line of data: 1501010005 444.25 1624680 1 04/05/15 11:40:51 1005 User009 108 1 308 One column of data: 1501010005 One column of data: 444.25 One column of data: 1624680 One column of data: 1 One column of data: 04/05/15 11:40:51 One column of data: 1005 One column of data: User009 One column of data: 108 One column of data: 1 One column of data: 308 |
通常情況下使用read命令可以讀取配置文件中的配置信息。