文章目录
一、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
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可方式脚本继续执行多余的代码。程序多出口通常并不是一个好现象(会导致程序逻辑难以理解),但是在上述脚本中适用。