天天看點

一站式搞定Bash腳本的參數處理問題

以下是來自StackOverflow網站的答案中的代碼,寫的實在是太好了,引用在這裡,以供查閱。

第一段代碼,這個例子展示了如何解析處理以空格分隔的參數(例如:–-option argument 這樣的傳參方式),不使用getopt和getopts函數來實作。

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47      
#!/bin/bash

# Usage: /tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done

set -- "${POSITIONAL[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi      

一些說明:

  • POSITIONAL=()   ---  聲明了一個空的數組。詳見​​這裡​​。
  • $#  --- 這是一個表示參數個數的特殊變量。詳見這裡。
  • $1, $2  --- 表示第一個參數,第二個參數的特殊變量。當然,$0表示第零個參數,一般儲存的是腳本的名稱。詳見這裡。
  • “$1”  --- 被雙引号用包覆的内容,将被視為單一字串。它防止通配符擴充,但允許變量擴充。這點與單引号的處理方式不同。被單引号用包覆的内容,将被視為單一字串。在引号内的代表變量的$符号,沒有作用,也就是說,它被視為一般符号處理,防止任何變量擴充。詳見這裡。
  • shift --- Shift指令一次移動參數的個數由其所帶的參數指定。如果不帶參數,則每次運作shift, 銷毀一個參數,後面的參數前移。詳見​​這裡​​。
  • POSITIONAL+=("$1")  --- 向數組的最末的位置插入一個元素。詳情見​​這裡​​。
  • ${POSITIONAL[@]}  --- 這個文法的意思是取回數組中的所有變量,${arr[@]}。詳情見​​這裡​​。
  • tail –1 “$1”  --- tail 指令可用于檢視檔案的内容, tail –1 somefile.txt 會把somefile.txt的最後一行列印在螢幕上。
  • if [[ -n $1 ]];  ---  [[ 可以了解為更進階更現代的[, 即test指令。這裡的文法對于用慣了進階語言的程式員會看起來很不習慣,感覺像if的條件裡的比較少了一個操作數一樣。把雙方括号換成test指令就好了解了, 大概是這個樣子:if (test –x option) then …
  • test 指令有很多參數.
  • -n 的意思是 “the length of STRING is nonzero”.
  • -f 的意思是 “FILE exists and is a regular file”.

第二段代碼,這個例子展示了如何門解析處理以等号分隔的參數(例如:–-option=argument 這樣的傳參方式),不使用getopt和getopts函數實作。

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37      
#!/bin/bash

# Usage: /tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi      
  • ${i#*=}  --- 這裡的語義通過上下文能看出來是移除了“-e=conf”這樣一個字元串裡的值為“-e=”的子字元串。簡單的文法是${string#substring},即從string的前頭開始,删除最短的substring的一個比對,詳情看​​這裡​​的“Substring Removal”的部分。這個語句中的*是一個通配符(wildcard),*=的意思是任意字元直到遇到等号的子字元串。關于删除子字元串,​​這裡​​給出了一個删除字首和字尾的例子。

第三段代碼,使用getopts函數實作。

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30      
#!/bin/sh

# Usage: /tmp/demo-getopts.sh -vf /etc/hosts foo bar

# A POSIX variable
# Reset in case getopts has been used previously in the shell.
OPTIND=1         

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

shift $((OPTIND-1))

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"      
  • $((OPTIND-1)) --- 這條語句中有兩個知識點:
  • $(( )) 的功能是進行算術運算,括号中的内容為數學表達式,使用 $(( )) 可以求數學表達式的值。這裡的意思就是說會對OPTIND-1這個表達式進行算術運算,用以求值。詳情請看這裡。
  • OPTIND 函數getopts配合case來進行操作時有兩個隐含變量:一個是OPTARG,用來取目前選項的值,另外一個是OPTIND,代表下一個要處理的元素位置。OPTIND是一個特殊的變量,它的初始值是1,每次getopts處理完一個指令參數後就遞增它,得到getopts要處理的下一個參數。詳情見​​這裡​​和這裡。
  • getopts "h?vf:"   ---  這裡的意思是getops 接受-h –v –f 為合規選項,?問号的意思就是不合規的選項進來的時候,就會執行給問好設定好的代碼,程式員可以利用這個機制給使用者提供恰當的資訊指導。冒号的意思是關掉系統預設的處理不合規的選項的方式(disable the default error handling of invalid options),建議關掉系統預設的處理不合規選項。
  • 優勢:
  • 可移植性更好,其他的類似的shell比如dash,可以相容。
  • 能自動地處理多重單字母選項,比如-vf filename, 這也是Unix的典型的處理方法。
  • 劣勢:
  • 如果不額外添加代碼的話,隻能處理短選項,比如-h可以,但--help 就不行。