第二十四章 流程控制: if分支和輸入
正如許多編程語言一樣Shell也有自己的條件分支語句。有時需要根據情況進行相應的處理,因此可以通過條件分支語句實現,本章主要介紹的是if分支語句。
if語句
在Shell中if語句語法格式如下:
if commands; thencommands
[elif commmands; thencommands...]
[elsecommands]
fi
其中commands是命令列表。
例如根據變量x是否為5輸出其結果信息:
x=5if [ "$x" -eq 5 ]; thenecho "x equals 5."
elseecho "x does not equal 5."
fi
執行結果如下圖:
本例中,將變量設置為5,通過if語句進行判斷,結果為真,因此輸出’x equals 5.‘’。
使用test
Shell不像其他編程語言直接使用條件表達式進行條件判定,在Shell中使用test進行各種檢查比較。該命令有兩種形式。第一種:
test expression
第二種,也是更流向的形式:
[ expression ]
其中,expression是一個表達式,求值結果要么為真,要么為假。如果為test返回推出狀態值0;如果為假,則返回1。
test和[其實都是命令。在bash中,兩者均為內建命令,但也作為獨立的可執行文件存在與/usr/bin中,供其它Shell使用。表達式其實就是[命令的參數,該命令同時要求將]作為其最后一個參數。
文件表達式
test文件表達式
表達式 | 什么情況下為真 |
---|---|
file1 -ef file2 | file1和file2都具有相同的i節點編號(兩個文件名通過硬鏈接指向同一個文件) |
file1 -nt file2 | file1比file2新 |
file1 -ot file2 | file1比file2舊 |
-b file | file存在且為塊設備文件 |
-c file | file存在且為字符設備文件 |
-d file | file存在且為目錄 |
-e file | file存在 |
-f file | file存在且為普通文件 |
-g file | file存在且設置了SGID位 |
-G file | file存在且為有效組ID所有 |
-k file | file存在且設置了"粘滯位" |
-L file | file存在且為符號鏈接 |
-O file | file存在且為有效用戶ID所有 |
-p file | file存在且為具名管道 |
-r file | file存在且可讀 |
-s file | file存在且不為空 |
-S file | file存在且為網絡套接字 |
-t fd | fd是與終端關聯的文件描述符。該表達式可用于判斷標準輸入/輸出/錯誤是否被重定向 |
-u file | 文件存在且設置SUID位 |
-w file | file存在且可寫(有效用戶具有寫權限) |
-x file | file存在且可執行(有效用戶具有執行/搜索權限) |
例如:判讀文件~/.bashrc的文件類型及其文件狀態
代碼如下:
#!/bin/bash# test-file:評估文件的狀態FILE=~/.bashrcif [ -e FILE ]; then#判斷文件類型if [ -f FILE ]; thenecho "$FILE is a regular file."elif [ -d FILE ]; thenecho "$FILE is a directory"elif [ -b FILE ]; thenehco "$FILE is a block device file"elif [ -c FILE ]; thenecho "$FILE is a character device file"elif [ -L FILE ]; thenecho "$FILE is a symbol link file"elif [ -S FILE ]; thenecho "$FILE is a socket file"elif [ -p FILE ]; thenecho "$FILE is a pipe file"fiif [ -r FILE ]; thenecho "$FILE is readable."fiif [ -w FILE ]; thenecho "$FILE is writable."fiif [ -x FILE ]; thenecho "$FILE is executable/searchable"fielseecho "$FILE does not exist"exit 1
fi exit
運行結果如下:
字符串表達式
test的字符串表達式
表達式 | 什么情況下為真 |
---|---|
string | string不為空 |
-n string | string的長度大于0 |
-z string | string的長度等于0 |
string1 == string2 | string1和string2相同。也可以使用單等號,但最好使用雙等號其不符號POSIX標準 |
string1 != string2 | string1和string2不相同 |
string1 > string2 | string1的排序位于string2之后 |
string1 < string2 | string1的排序位于string2之前 |
在使用test時,必須將表達式操作符>和<引用起來(或者通過反斜線轉義)。如果不這么做,兩者會被Shell解釋為重定向操作符,有可能會造成破壞性后果。另外還要注意,盡管Bash文檔中說過排序遵從當前語言環境的排序規則,但事實并非如此。一直到4.0版本,Bash一直使用的是ASCII(POSIX)排序。這個問題在4.1版本中才糾正過來。
例如:計算字符串的值
代碼如下:
#!/bin/bash#test-string:計算字符串值ANSWER=maybeif [ -z "$ANSWER" ]; thenecho "There is no answer." >&2exit 1
fiif [ "$ANSWER" = "yes" ]; thenecho "The answer is YES."
elif [ "$ANSWER" = "no" ]; thenecho "The answer is NO."
elif [ "$ANSWER" = "maybe" ]; thenecho "The answer is MAYBE."
elseecho "The answer is UNKNOWN."
fi
該程序運行結果如下:
整數表達式
test的整數表達式
表達式 | 什么情況下為真 |
---|---|
integer1 eq interger2 | integer1等于interger2 |
integer1 ne interger2 | integer1不等于integer2 |
integer1 -le integer2 | integer1小于或等于integer2 |
integer1 -lt integer2 | integer1小于integer2 |
integer1 -ge integer2 | integer1大于或等于integer2 |
integer1 -gt integer2 | integer1大于integer2 |
例如:計算整數的值
代碼如下:
#!/bin/bash#test-integer: 計算整數值INT=-5if [ -z "$INT" ]; thenecho "INT is empty." >&2exit 1
fiif [ "$INT" -eq 0 ]; thenecho "INT is zero."elseif [ "$INT" -lt 0 ]; thenecho "INT is negative."elseecho "INT is positive."fiif [ $((INT % 2)) -eq 0 ]; thenecho "INT is even."elseecho "INT is odd."fi
fi
運行結果如下:
更現代的test
Bash的現代版本包含了一個可以作為test增強版的復合命令,其用法如下:
[[ expression ]]
和test一樣,其中的expression是一個表達式,結果要么為真,要么為假。[[]]命令(支持所以test表達式)類似于test另外還加入一個重要的全新的字符串表達式。
string1 =~ regex
如果string1匹配ERE regex,則返回為真。例如在整數表達式的例子中,如果常量INT含有整數以為的其它值,腳本就會執行失敗。腳本需要一種方法來核實該常量包含的是整數,可以使用[[]]配合=~字符傳表達式,按照下列方式改進腳本:
#!/bin/bash#test-integer2: 計算整數值INT=-5if [[ "$INT" =~ ^-?[0-9]+$ ]]; thenif [ "$INT" -eq 0 ]; thenecho "INT is zero."elseif [ "$INT" -le 0 ]; thenecho "INT is negative."elseecho "INT is positive."fiif [ $((INT % 2)) -eq 0 ]; thenecho "INT is even."elseecho "INT is odd."fifi
elseecho "INT is not an integer." >&2exit 1
fi
運行結果如下:
從運行結果來看和之前的例子運行結果相同,通過正則表達式可以判斷是否為數字。將INT的值限制為只能是以可選的減號起始,后跟一個或多個數字的字符串,同時也消除了INT為空值的可能。
[[]]的另一個特征是其中的==操作符支持和路徑擴展一樣的模式匹配。
(())-為整數設計
除了復合命令[[]],bash還支持另一種復合命令(()),它在整數操作時用得上。該命令支持所有的算術求值。
(())可用于執行算術真值測試( arithmetic truth test)。如果算術求值的結果不為0,則測試結果為真。
有了(()),可以簡化一下test-integer2的副本:
#!/bin/bash#test-integer2:計算整數的值INT=-5if [ "$INT" =~ ^-?[0-9]+$ ]; thenif ((INT == 0)); thenecho "INT is zero."elseif ((INT < 0)); thenecho "INT is negative."elseecho "INT is positive."fiif (( ((INT % 2)) == 0 )); thenecho "INT is even."elseecho "INT is odd."fielseecho "INT is not an integer." >&2exit 1
fi
(())復合命令是Shell語法的一部分,而非普通命令,并且只能處理整數,因此它能夠通過名稱來識別變量,不需要執行擴展操作。
組合表達式
將多個表達式組合在一起,形成更為復雜的測試。表達式通過邏輯操作符組合起來。test和[[]]可用的邏輯操作有3種,分別是AND、OR、NOT。test和[[]]使用不同的操作符來表示這些邏輯操作。
邏輯操作符
操作 | test | [[]]和(()) |
---|---|---|
AND | -a | && |
OR | -o | || |
NOT | ! | ! |
例如:判斷整數是否位于特定取值區間內
代碼如下:
#!/bin/bash#test-integer3:確定整數是否位于特定取值區間內MIN_VAL=1
MAX_VAL=100INT=50if [[ "$INT" =~ ^-?[0-9]+$ ]]; thenif [[ "$INT" -ge "$MIN_VAL" && "$INT" -le "$MAX_VAL" ]]; thenecho "$INT is within $MIN_VAL to $MAX_VAL."elseecho "$INT is out of range."fi
elseecho "INT is not an integer." >&2exit 1
fi
運行結果如下:
在該腳本中通過[[]]實現,該操作符包含由&&分隔的兩個表達式。可以使用test改寫:
if [ "$INT" -ge "$MIN_VAL" -a "$INT" -le "$MAX_VAL" ]; thenecho "$INT is within $MIN_VAL to $MAX_VAL."
elseecho "$INT is out of range"
fi
否定操作符!會表達式的結果求反。例如要找出指定取值區間之外的INT值:
#!/bin/bash#test-interger4:確定整數是否位于指定取值區間之外MAX_VAL=100
MIN_VAL=1INT=50if [[ "$INT" =~ ^-?[0-9]+$ ]]; thenif [[ ! ("$INT" -ge "$MIN_VAL" && "$INT" -le "$MAX_VAL") ]]; thenecho "$INT is outside $MIN_VAL to $MAX_VAL."elseecho "$INT is in range."fielseecho "INT is not an integer." >&2exit 1
fi
運行結果如下:
在表達式兩邊加上括號,用于分組。如果不見,!僅用于第一個表達式,而非兩個表達式的組合。使用test的代碼如下:
if [ ! \( "$INT" -ge "$MIN_VAL" -a "$INT" -le "$MAX_VAL"\)]; thenecho "$INT is outside $MIN_VAL to $MAX_VAL."
elseecho "$INT is in range."
fi
test和[[]]的功能基本差不多,test是長期存在的標準(也是標準Shell的POSIX規范的一部分,多用于系統啟動腳本),而[[]]是Bash(包括其它少數現代Shell)專用的。
控制操作符:另一種分支方式
Bash提供了兩種可以執行分支的操作符,即&&(AND)和||(OR)操作符,它們類似于[[]]復合命令中的邏輯操作符。&&的語法如下:
command1 && command2
|| 的語法如下:
command1 || command2
對于&&的操作符,先執行command1,僅當command1執行成功時才執行command2。對于||操作符,先執行command1,僅當command1執行失敗時才執行command2。
例如:先創建目錄,然后執行cdmingl
mkdir temp && cd temp
該命令會創建一個temp的目錄,如果創建成功,就將當前的工作目錄更改為temp。第二個命令僅在mkdir命令執行成功的情況下執行。
測試目錄temp的存在,當不存在時創建temp目錄:
[[ -d temp ]] || mkdir temp
這種結構便于在腳本中處理錯誤。
read-從標準輸入讀取值
內建的read命令可以從標準輸入讀取一行。用法如下:
read [-options] [variable…]
其中options是選項,variable是一個或多個變量,用于保存輸入值。如果未指定變量,則輸入值保存在Shell變量REPLY中。
例如:對輸入的數值進行計算
代碼如下:
#!/bin/bash#test-integer4:計算整數的值echo -n "Please enter an integer ->"
read numif [[ "$num" =~ ^-?[0-9]+$ ]]; thenif [ "$num" -eq 0 ]; thenecho "$num is zero."fiif [ "$num" -lt 0 ]; thenecho "$num is negative."elseecho "$num is positive."fiif [ (( num % 2)) -eq 0 ]; thenecho "$num is even."elseecho "$num is odd."fielseecho "$int is not an integer." >&2exit 1
fi
輸出結果如下:
read選項
選項 | 描述 |
---|---|
-a array | 將輸入分配給數組(從索引0開始)。 |
-d delimiter | 將字符串delimiter中的第一個字符(而非換行符)作為輸入結束 |
-c | 使用Readline處理輸入。這允許使用和命令行相同的方式編輯輸入 |
-i string | 如果用戶直接按Enter鍵,使用string作為默認值。需要配合-e選項使用 |
-n num | 從輸入中讀取num個字符,而非讀取一行 |
-p prompt | 將字符串prompt作為輸入提示來顯示 |
-r | 原始模式(raw mode)。不將反斜線解釋為轉義 |
-s | 靜默模式。在用戶輸入字符時不回顯。該模式適用于輸入密碼或其它機密信息 |
-t seconds | 超時。seconds秒之后終止輸入。如果輸入超時,read返回非0退出狀態值 |
-u fd | 從文件描述符fd(而非標準輸入)中讀取輸入 |
例如:讀取“機密”輸入:
代碼如下:
#!/bin/bash#read-secret:輸入"機密"if read -t 10 -sp "Enter secret passprase >" sectet_pass; thenecho -e "\nSecret passphrase = '$secret_pass' "
elseecho -e "\nInput timed out" >&2exit 1
fi
程序運行結果如下:
IFS
Shell通常會對提供給read的輸入進行單詞分割。Shell變量內部字段分割符(Internal Field Separator , IFS)控制著此行為。IFS的默認值包含了空格符、制表符、換行符,它們到可用于分隔單詞。
例如:從文件讀取字段
#!/bin/bash#read-ifs: 從文件中讀取字段FILE=/etc/passwdread -p "Enter a username > " user_namefile_info="$(grep "^$user_name:" $FILE)" #查找包含user_name的值的所在行內容if [ -n "$file_info" ]; thenIFS=":" read user pw uid gid name home shell <<< "$file_info"echo "User= '$user'"echo "UID= '$uid'"echo "GID= '$gid'"echo "Full Name = '$name'"echo "Home Dir. = '$home'"echo "Shell = '$shell'"
elseecho "No such user '$user_name'" >&2exit 1
fi
運行結果如下:
注意:read命令不能放入管道也就是說下面這種用法是禁止的:
echo “foo” | read