\$0 表示程序名。
\$1 至 \$9則是位置參數。
\$# 表示參數的個數。
\$* 將全部參數當作一個總體來引用
\$@ 把每一個參數做爲一個字符串返回,可使用for循環來遍歷
\$? 最近一個執行的命令的退出狀態。0表示執行成功
\$_ 上一個命令的最後一個參數。使用快捷鍵 ESC+. 也是這個效果shell
位置參數不止9個,更多的參數也是同樣支持的。只是要使用\${10}這樣的形式引用。
\$1 和 \${1}的效果是同樣的。
不用花括號的話,\$10 會被認爲是 \$1 和一個字符 0。數組
帶空格的參數值
每一個參數都是用空格分隔的。要在參數值中包含空格,必需要使用引號(單引號或雙引號均可)。 bash
將文本字符串做爲參數傳遞時,引號並不是數據的一部分。它們只是代表數據的起止位置。ide
\$0 表示腳本名,可是不一樣的調用方法返回的結果也是不一樣的。下面的腳本就是簡單的打印\$0的值:測試
$ cat filename.sh #!/bin/bash echo $0 $ ./ filename.sh -bash: ./: 是一個目錄 $ cat filename.sh #!/bin/bash echo $0 $ ./filename.sh ./filename.sh $ bash filename.sh filename.sh $ bash /root/filename.sh /root/filename.sh $
使用 basename 命令
若是要使用腳本名稱來進行判斷,能夠先用命令 basename 把路徑的信息給過濾掉。命令的效果以下:命令行
$ basename /var/log/messages messages $
因此上面的腳本能夠修改爲這樣:指針
$ cat filename.sh #!/bin/bash echo $(basename $0) $ ./filename.sh filename.sh $ bash filename.sh filename.sh $ bash /root/filename.sh filename.sh $
在腳本中使用參數要確保參數存在,不然運行時有可能會報錯:code
$ cat add.sh #!/bin/bash echo $1 + $2 = $[ $1 + $2 ] $ ./add.sh 1 2 1 + 2 = 3 $ ./add.sh 1 ./add.sh:行2: 1 + : 語法錯誤: 期待操做數 (錯誤符號是 "+ ") $
若是隻是當作字符串引用,也不會報錯。沒有傳參的參數默認都是空:orm
$ cat hello.sh #!/bin/bash echo Hello $1 $2. $ ./hello.sh Tom Jerry Hello Tom Jerry. $ ./hello.sh Jerry Hello Jerry . $ ./hello.sh Hello . $
判斷參數是否存在
在 shell 中利用 -n 來斷定字符串非空,-z 則正好相反,空便是真。上面已經測試過了,未定義的參數默認是空:字符串
$ cat hello.sh #!/bin/bash if [ -n "$1" ] then echo Hello $1. else echo Hello Nobody. fi $ ./hello.sh Tom Hello Tom. $ ./hello.sh Hello Nobody. $
這裏的判斷的 \$1 要加上雙引號,不然會被認爲是字符串。一個字符串固然非空,因此結果會永遠爲真。
判斷參數的個數
上面的例子的腳本也能夠經過判斷參數數量是否大於0來實現:
$ cat hello.sh #!/bin/bash echo 參數數量: $# if [ $# -gt 0 ] then echo Hello $1. else echo Hello Nobody. fi $ ./hello.sh 參數數量: 0 Hello Nobody. $ ./hello.sh Tom 參數數量: 1 Hello Tom. $ ./hello.sh Tom Jerry 參數數量: 2 Hello Tom. $
這裏 -gt 比較的是先後兩個數字(INT),因此\$#是不加引號的。
用這種方法也能判斷參數是否存在。兩種方法,效果同樣,不一樣的書上都看到有人使用。
這裏是同樣加法的例子,必需要傳入2個參數:
$ cat add.sh #!/bin/bash if [ $# -eq 2 ] then echo $1 + $2 = $[ $1 + $2 ] else echo 須要參數: 2, 實際參數: $#. fi $ ./add.sh 1 2 1 + 2 = 3 $ ./add.sh 1 2 3 須要參數: 2, 實際參數: 3. $ ./add.sh 1 須要參數: 2, 實際參數: 1. $
若是要表示不相等,就是 if [ $# -ne 2 ]
獲取最後一個參數
這是一個使用 \$# 的小技巧。使用${$#}
彷佛就是參數的最後一個變量了。
可是其實否則,花括號裏不能這樣用\$,這裏要把裏面的換成感嘆號:
$ cat hello.sh #!/bin/bash if [ $# -gt 0 ] then echo Hello ${!#}. else echo Hello Nobody. fi $ ./hello.sh Tom Jerry Hello Jerry. $
若是沒有任何命令行參數,那麼就是返回\$0,也就是腳本名。
上面感嘆號的問題,效果是引用變量的值而不是變量自身。相似於指針的取值。把#號換成一個有名字的變量來講明比較直觀:
$ cat parameter.sh #!/bin/bash paramater=key key=value echo "${paramater}" echo "${!paramater}" echo "${key}" $ ./parameter.sh key value value $
不加感嘆號,就是直接去該變量的值。加上感嘆號,就是去變量值所對應的變量名的那個變量的值。
\$* 和 \$@ 都是表示全部的字符串,可是在遍歷的時候會有區別:
$ cat all.sh #!/bin/bash echo '$* 的效果:' count=1 for i in "$*" do echo $count: $i count=$[ $count + 1 ] done echo '$@ 的效果:' count=1 for i in "$@" do echo $count: $i count=$[ $count + 1 ] done $ ./all.sh Oliver Barry Kara Sara Kane $* 的效果: 1: Oliver Barry Kara Sara Kane $@ 的效果: 1: Oliver 2: Barry 3: Kara 4: Sara 5: Kane $
\$*就一個總體的值,沒法遍歷。要遍歷每個變量要使用\$@。這裏的雙引號很重要。
不加引號的話,就是把 \$* 和 \$@ 的內容(變量解析後就是多個詞)傳遞給for循環遍歷,這樣兩個參數的效果是同樣的。和直接傳不加引號的字符串的效果同樣。
加上引號,引號裏的內容就是一個總體。若是是\$*,這個總體裏的全部內容仍是一個詞,不會拆。若是是\$@,這個總體裏會按空格拆分紅多個詞。
下面是演示的效果:
$ cat all2.sh #!/bin/bash echo '$* 不加引號的效果:' count=1 for i in $* do echo $count: $i count=$[ $count + 1 ] done echo '$@ 不加引號的效果:' count=1 for i in $@ do echo $count: $i count=$[ $count + 1 ] done echo '直接遍歷不加引號的字符的效果:' count=1 for i in Oliver Barry Kara Sara Kane do echo $count: $i count=$[ $count + 1 ] done echo '加引號遍歷的效果:' count=1 for i in "Oliver Barry Kara Sara Kane" do echo $count: $i count=$[ $count + 1 ] done $ ./all2.sh Oliver Barry Kara Sara Kane $* 不加引號的效果: 1: Oliver 2: Barry 3: Kara 4: Sara 5: Kane $@ 不加引號的效果: 1: Oliver 2: Barry 3: Kara 4: Sara 5: Kane 直接遍歷不加引號的字符的效果: 1: Oliver 2: Barry 3: Kara 4: Sara 5: Kane 加引號遍歷的效果: 1: Oliver Barry Kara Sara Kane $
強調:特殊參數\$@必定要用在雙引號內,效果是每一個參數都擴展爲分隔的單詞。在使用for循環遍歷的時候會體現出效果。
shift 命令可以用來操做命令行參數。默認狀況下將每一個參數向左移動一個位置。被移出的參數就被丟棄了,沒法恢復。
先掌握這個命令的使用,使用這個命令能夠方便地解析命令行參數。
下面是一個簡單的示例:
$ cat pop.sh #!/bin/bash count=1 while [ -n "$1" ] # while [ $# -ne 0 ] do echo "$count: $1" count=$[ $count + 1 ] shift done $ ./pop.sh Oliver Barry Kara Sara Kane 1: Oliver 2: Barry 3: Kara 4: Sara 5: Kane $
這裏有2中判斷方法來判斷是否還有參數,效果是同樣的。
帶參數執行shift,指明要移動幾個位置就能夠了:
$ cat pop.sh #!/bin/bash count=1 # while [ -n "$1" ] while [ $# -ne 0 ] do if [ -n "$2" ] then echo "$count: $1, $2" shift 2 else echo "$count: $1" shift fi count=$[ $count + 1 ] done $ ./pop.sh Oliver Barry Kara Sara Kane 1: Oliver, Barry 2: Kara, Sara 3: Kane $
簡單修改下上面的腳本,一次輸出2個參數,而後移動2個位置。
當shell腳本須要多個命令行參數時,在調用腳本的時候就必須將全部參數按固定的順序。
或者還可使用選項來指定參數的值。
這個例子裏有帶值的選項也有不帶值的選項:
$ cat format.sh #!/bin/bash prefix="" # 前綴 base="test" # 默認字符串 suffix="" # 後綴 upper=off # 是否大寫 # 解析命令行參數 while [ -n "$1" ] do case "$1" in -a) suffix="$2" shift ;; -b) prefix="$2" shift ;; -s) base="$2" shift ;; -u) upper=on ;; *) echo "$1 is not an option" exit 1 ;; # 發現未知參數,直接退出 esac shift done # 添加前綴和後綴 output="${prefix:+${prefix}_}${base}${suffix:+_${suffix}}" # 判斷是否要全大寫輸出 if [ $upper = on ] then output=${output^^} fi # 輸出結果 echo "$output" $ ./format.sh -a after test_after $ ./format.sh -s hello -b befor befor_hello $ ./format.sh -s hello -u -a after -b befor BEFOR_HELLO_AFTER $ ./format.sh -s hello -u -a after -b befor -l -l is not an option $
case語句找到一個選項就處理一個選項。若是還須要在命令行提供其餘參數,能夠在通用狀況的處理部分中處理。而這裏由於不須要提供任何參數,凡是解析不正確的就報告錯誤並退出(exit 1)。
能解析參數的版本
這個版本匹配全部的參數進行格式化輸出:
$ cat format.sh #!/bin/bash prefix="" # 前綴 base="test" # 默認字符串 suffix="" # 後綴 upper=off # 是否大寫 # 顯示聲明一下這是個數組變量,其實沒有必要 declare -a names # 須要格式化輸出的全部原始字符串 # 解析命令行參數 while [ -n "$1" ] do case "$1" in -a) suffix="$2" shift ;; -b) prefix="$2" shift ;; -s) base="$2" shift ;; -u) upper=on ;; *) names=("${names[@]}" "$1") ;; esac shift done names[0]=${names[0]:-$base} for name in "${names[@]}" do # 添加前綴和後綴 output="${prefix:+${prefix}_}${name}${suffix:+_${suffix}}" # 判斷是否要全大寫輸出 if [ $upper = on ] then output=${output^^} fi # 輸出結果 echo "$output" done $ $ ./format.sh -a after -b befor -u value1 value2 value3 BEFOR_VALUE1_AFTER BEFOR_VALUE2_AFTER BEFOR_VALUE3_AFTER $ ./format.sh -a after after1 -b befor befor1 -u value1 value2 value3 BEFOR_AFTER1_AFTER BEFOR_BEFOR1_AFTER BEFOR_VALUE1_AFTER BEFOR_VALUE2_AFTER BEFOR_VALUE3_AFTER $ ./format.sh -a after after1 -b befor befor1 -u -v value1 value2 value3 BEFOR_AFTER1_AFTER BEFOR_BEFOR1_AFTER BEFOR_-V_AFTER BEFOR_VALUE1_AFTER BEFOR_VALUE2_AFTER BEFOR_VALUE3_AFTER $
看最後的兩項的結果,提供的命令行參數有問題,可是程序沒法發現。
倒數第二項能夠認爲提供的參數是對的,可是選項和參數交替出現。
而最後一項提供了一個錯誤的選項,可是沒法識別出來。
解決這個問題,須要更加規範的方法來分離參數和選項。下一小節的內容。
數組帶空格的問題
數組添加元素有不少方法,這裏是一種從新建立數組的作法:
array_name=("${array_name[@]}" value1 ... valueN)
能夠一次添加多個元素,若是字符串包含空格,就要加上引號。
和命令行參數的\$@與\$*同樣,數組全部的元素也有這兩個相似的符號。最嚴謹的方法是使用 "${names[@]}"
使用帶雙引號的@。
添加元素和取出元素的時候都要注意,不然存在帶空格的元素的時候就會破壞數組本來的元素分隔。
添加元素這裏使用:
names=("${names[@]}" "$1")
不單是數組裏的元素,被添加的元素也要加上雙引號,不然若是有空格,就會按多個元素被添加進數組。
遍歷元素使用:
for name in "${names[@]}"
只有添加的時候正確了,才能正確的遍歷。而後遍歷的時候也要保證正確。
驗證效果:
$ ./format.sh -a after -b befor -u value1 "value2 value3" value4 BEFOR_VALUE1_AFTER BEFOR_VALUE2 VALUE3_AFTER BEFOR_VALUE4_AFTER $
完美。
這裏的參數就是命令行參數中除了定義的選項以外,其餘額外的參數。要同時處理參數和選項,就要用特殊字符(雙破折線--)將兩者分開。雙破折線代表選項列表結束,雙破折線後面的都是參數。基於這個邏輯,只要在case語句中加一項判斷就好了。
把上面的腳本作一些修改:
$ cat format.sh #!/bin/bash prefix="" # 前綴 base="test" # 默認字符串 suffix="" # 後綴 upper=off # 是否大寫 # 顯示聲明一下這是個數組變量,其實沒有必要 declare -a names # 須要格式化輸出的全部原始字符串 # 解析選項 while [ -n "$1" ] do case "$1" in -a) suffix="$2" shift ;; -b) prefix="$2" shift ;; -s) base="$2" shift ;; -u) upper=on ;; --) shift break ;; *) echo "$1 is not an option" exit 1 ;; # 發現未知參數,直接退出 esac shift done # 解析參數 while [ -n "$1" ] do names=("${names[@]}" "$1") shift done names[0]=${names[0]:-$base} for name in "${names[@]}" do # 添加前綴和後綴 output="${prefix:+${prefix}_}${name}${suffix:+_${suffix}}" # 判斷是否要全大寫輸出 if [ $upper = on ] then output=${output^^} fi # 輸出結果 echo "$output" done $
基於這個版本,在使用的時候,須要先輸入選項,而後使用雙破折線隔開,再輸入參數。當腳本遇到雙破折線時,它會中止處理選項,並將剩下的參數都看成參數:
$ ./format.sh -a after -b befor -u value1 value2 value3 value1 is not an option $ ./format.sh -a after -b befor -u -- value1 value2 value3 BEFOR_VALUE1_AFTER BEFOR_VALUE2_AFTER BEFOR_VALUE3_AFTER $ ./format.sh -a after -b befor -v -u -- value1 value2 value3 -v is not an option $
第一次沒有使用雙破折線,因此報錯。
第二次正確的用雙破折號分隔了參數和選項。
第三次在選項部分出現了未定義的選項,也能發現錯誤。
小結
這一小節的內容也是爲下面的getopt命令作鋪墊。getopt就是能夠幫咱們完成命令行參數的解析,返回一個用雙破折線隔開選項和參數的規整的參數列表。
另外這裏還不支持選項合併:
$ ls -al
這些問題,用getopt都能解決,並且還支持長選項。