文章目录

一、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的运作方式如下:

编写shell脚步--读取键盘输入_标准输入


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