一、read–從標準輸入讀取輸入值
內嵌命令read
的作用是讀取一行標準輸入。此命令可用於讀取鍵盤輸入值或應用重定向讀取文件中的一行。read
命令的語法結構如下所示:
read [-options] [variable...]
語法中options爲下表中列出的一條或多條可用的選項,而variable則是一到多個用於存放輸入值的變量。若沒有提供任何此類變量,則由shell變量REPLY來存儲數據行。
基本上,read
命令將標準輸入的字段值分別賦給指定的變量。若使用read
命令改寫之前的整數驗證腳本,如下所示:
#!/bin/bash
#read-integer: evaluate the value of a integer:
echo -n "Please enter an integer ->"
read INT
if [[ "$INT" =~ ^-[0-9]+$ ]]; then
if [ "$INT" -eq 0 ]; then
echo "INT is zero."
else
if [ $INT -lt 0]; then
echo "INT is negative."
else
echo "INT is positive."
fi
if [ $((INT % 2)) -eq 0]; then
echo "INT is even."
else
echo "INT is odd."
fi
fi
else
echo "INT is not an integer." >&2
exit1
fi
我們使用帶有-n選項(時接下來的輸出在下一行顯示)的echo
命令來輸出一條提示符,然後使用read命令給INT變量賦值。
在下述腳本中,read
命令將輸入值賦給多個變量。
#!/bin/bash
# read-multiple: read multiple values from keyboard
echo -n "Enter one or more values > "
read var1 var2 var3 var4 var5
echo "var1 = '$var1' "
echo "var1 = '$var2' "
echo "var1 = '$var3' "
echo "var1 = '$var4' "
echo "var1 = '$var5' "
此腳本可以給5個變量賦值並輸入。需要注意當輸入少於或多於5個值的時候,read的運作方式如下:
若read
命令讀取的值少於預期的數目,則多餘的變量值爲空,而輸入值的數目超出預期的結果時,最後的變量包含了所有的多餘值。
如果read
命令之後沒有變量,則會爲所有的輸入分配一個shell變量:REPLY。
#!/bin/bash
#read-signal: read multiple values into default variable
echo -n "Enter one or more values > "
read
echo "REPLY = '$REPLY' "
以上腳步的運行結果如下所示。
$ read-single
Enter one or more values > a b c d
REPLY = 'a b c d'
1.1、選項
read選項
選項 | 描述 |
---|---|
-a array | 將輸入值從索引爲0的位置開始賦給array |
-d delimiter | 用字符串delimiter的第一個字符標誌輸入的結束,而不是新的一行的開始 |
-e | 使用Readline處理輸入。此命令使用戶能使用命令行模式的相同方式編輯輸入 |
-n num | 從輸入中讀取num個字符,而不是一整行 |
-p prompt | 使用prompt字符串提示用戶進行輸入 |
-r | 原始模式,不能將後斜線字符翻譯爲轉義碼 |
-s | 保密模式。不在屏幕顯示輸入的字符。此模式在輸入莫馬和其它機密信息時很有用處 |
-t seconds | 超時。在seconds秒後結束輸入。若輸入超時,read 命令返回一個非0的退出狀態 |
-u fd | 從文件說明符fd讀取輸入,而不是從標準輸入中讀取 |
使用不同的選項,read命令可以達成不同的有趣的效果。例如,可使用-p選項來顯示提示符
#!/bin/bash
# read-sibgle: read multiple values into default varible
read -p "Enter one or more values > "
echo "REPLY = '$REPLY' "
使用-t與-s選項,可以寫出讀取“祕密”輸入的腳本,此腳本若一定時間內沒有完成輸入,會造成超時。
#!/bin/bash
# read-secret: int a secret passphrase
if read -t -sp "Enter secret passphrase > " secret_pass; then
echo -e "\nSecret passphrase = '$secret_pass' "
else
echo -e "\nInput timed out" >&2
exit 1
fi
上訴腳本會提示用戶輸入祕密通行短語,系統的等待時間爲10廟。若10秒內用戶沒有完成輸入,腳本以出錯狀態結束運行。又因爲使用了-s選項,輸入的密碼並不會顯示到屏幕上。
1.2、使用IFS間隔輸入字段
通常shell會間隔提供給read
命令的內容。這也就意味着,在輸入行,由一到多個空格將多個單詞分隔成爲分離的單項,再由read
命令將這些單項賦值給不同的變量。此行爲是由shell變量IFS(Internal Field Separator)設定的。IFS的默認值包含了空格、製表符和換行符,每一種都可以將字符彼此分隔開。
我們可以通過改變IFS值來控制read
命令輸入的間隔方式。例如,文件/etc/passwd的內容使用冒號作爲字段之間的間隔符。將IFS的值改爲單個冒號即可使用read命令讀取/etc/passwd文件的內容,併成功將各字段分隔爲不同的變量。下面的腳本便完成了此功能。
#!/bin/bash
# read-ifs: read fields from a file
FILE=/etc/passwd
read -p "Enter a username > " user_name
file_info=$(grep "^$user_name:" $FILE)
if [ -n "$file_info" ]; then
IFS=":" 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' "
else
echo "No such user '$user_name' " >$2
exit 1
fi
此腳本提示用戶輸入系統賬戶的用戶名,根據用戶名找到/etc/passwd文件中相應的用戶記錄,並輸出此記錄的各個字段。此腳本包含了兩行有趣的代碼。第一行是file_info=$(grep "^$user_name:" $FILE)
,他將grep
命令的結果賦值給file_info變量。grep
命令使用的正則表達式保證了用戶名只會與/etc/passwd文件中的一條記錄相匹配。
第二行是IFS=":" read user pw uid gid name home shell <<< "$file_info"
,由三部分構成,即一條變量賦值語句、一條帶有變量名作參數的read
命令和一個陌生的重定向運算符。首先讓我們看一下其中的變量賦值語句。
shell允許在命令執行之前對一到多個變量進行賦值,這些賦值操作會改變接下來所執行命令的操作環境。但是賦值的效果是暫時性的,只有在命令執行週期內有效。本例中,IFS的值被修改爲一個冒號。我們也可以通過以下方式達到此效果。
OLD_IFS = "$IFS"
IFS=":"
read user pw uid gid name home shell <<< "$file_info"
IFS="$OLD_IFS"
首先我們儲存了IFS的舊值,並將新值賦給IFS,我們執行了read
命令最後將IFS恢復原值。顯然,對於做相同的事情,將變量賦值語句置於執行命令前,是更爲簡潔的方法。
操作符“<<<”象徵一條嵌入字符串。嵌入字符串與嵌入文檔類似,只不過更爲簡短,它包含的是一條字符串。本例將/etc/passwd文件中讀取的數據輸送給read
命令。
注意:read不可重定向。通常read命令會從標準輸入中獲取輸入,而不能採用這種方式:
echo "foo" | read
二、驗證輸入
在程序具備了讀取鍵盤輸入功能的同時,也帶來了新的編程上的挑戰–驗證輸入。通常來說,程序寫得好與不好的差別僅僅在於程序是否能夠處理突發意外情況。而意外情況經常以錯誤輸入的形式出現。對所有程序接收的輸入執行此類驗證是防禦無效數據的重要方式,且對於多用戶共享的程序尤其重要。只有那些執行特殊任務且只會被作者使用一次的程序,處於經濟學原理可以省略這些安全措施。儘管如此,若程序執行的是像刪除文件這樣的危險任務,爲以防萬一海斯進行數據驗證會比較好。
以下是一個對不同類型輸入進行驗證的程序。
#!/bin/bash
# read-validate: validate input
invalid_input () {
echo "Invalid input '$REPLY' " >&2
exit 1
}
read -p "Enter a single item > "
#input is empty (invaild)
[[ -z $REPLY ]] && invalid_input
#input is multiple items (invalid)
(( $(echo $REPLY | wc -w) > 1 )) && invalid_input
#is input avalid filename?
if [[ $REPLY =~ ^[ -[:alnum:]\._ ]+$ ]]; then
echo "'$REPLY' is valid filename. "
if [[ -e $REPLY ]]; then
echo "And file '$REPLY ' exists. "
else
echo "However, file '$REPLY' does not exist."
fi
#is input a floating point number?
if [[ $REPLY =~ ^-?[[:digit:]]*\.[[:digit:]]+$ ]]; then
echo "'$REPLY' is a floating point number."
else
echo "'$REPLY' is not a floating point number."
fi
#is input an integer?
if [[ $REPLY =~ ^-?[[:digit:]]+$ ]]; then
echo "'$REPLY' is an integer."
else
echo "'$REPLY' is not an integer."
fi
else
echo "The string '$REPLY' is not a valid filename"
fi
此腳本首先提示用戶進行輸入,然後分析確定用戶的輸入。腳本使用了shell函數、[[]]、(())、控制操作符&&、if以及適量的正則表達式。
三、菜單
菜單驅動是一種常見的交互方式。在菜單驅動的程序會呈現給用戶一系列的選項,並請求用戶進行選擇。例如,可想象程序呈現的菜單如下所示。
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
4. Quit
Enter selection [0-3] >
我們可以構建菜單驅動的程序來執行上述菜單中各項任務。
#!/bin/bash
# read-menu: a menu driven system information program
clear
echo "
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
4. Quit
"
read -p "Enter selection [0-3] > "
if [[ $REPLY =~ ^[0-3]$ ]]; then
if [[$REPLY == 0 ]]; then
echo "Program terminated."
exit
fi
if [[ $REPLY ==1 ]]; then
echo "Hostname: $HOSTNAME"
exit
fi
if [[ $REPLY == 2 ]]; then
df -h
exit
fi
if [[ $REPLY == 3 ]]; then
if [[ $(id -u) -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh $HOME
fi
exit
fi
else
echo "Invalid entry." >&2
exit 1
fi
腳本在邏輯上分隔爲兩個部分。第一部分展示了菜單並獲取用戶的響應;第二部分對響應進行驗證並執行相應的選項。需要注意腳本中的exit
命令的使用方法。在完成選定的功能後,exit可方式腳本繼續執行多餘的代碼。程序多出口通常並不是一個好現象(會導致程序邏輯難以理解),但是在上述腳本中適用。