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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章