Linux程序设计:读取键盘输入

来源:互联网 发布:java包名命名规范 编辑:程序博客网 时间:2024/05/16 14:39
到目前为止我们编写的脚本都缺乏一项在大多数计算机程序中都很常见的功能-交互性。也就是,程序与用户进行交互的能力。虽然许多程序不必是可交互的,但一些程序却得到益处,能够直接接受用户的输入。以这个前fi面章节中的脚本为例:

#!/bin/bash

# test-integer2: evaluate the value of an integer.
INT=-5
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
exit 1
每次我们想要改变INT 数值的时候,我们必须编辑这个脚本。如果脚本能请求用户输入数值,那么它会更加有用处。在这个脚本中,我们将看一下我们怎样给程序增加交互性功能。

read-从标准输入读取数值

这个read 内部命令被用来从标准输入读取单行数据。这个命令可以用来读取键盘输入,当使用重定向的时候,读取文件中的一行数据。这个命令有以下语法形式:
read [-options] [variable...]
这里的options 是下面列出的可用选项中的一个或多个,且variable 是用来存储输入数值的一个或多个变量名。如果没有提供变量名,shell 变量REPLY 会包含数据行。基本上,read 会把来自标准输入的字段赋值给具体的变量。如果我们修改我们的整数求值脚本,让其使用read ,它可能看起来像这样:

#!/bin/bash
# read-integer: evaluate the value of an 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 "Input value is not an integer." >&2
exit 1
fi

我们使用带有-n 选项(其会删除输出结果末尾的换行符)的echo 命令,来显示提示信息,然后使用read 来读入变量int 的数值。运行这个脚本得到以下输出:
[me@linuxbox ~]$ read-integer
Please enter an integer -> 5
5 is positive.
5 is odd.
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 "var2 = '$var2'"
echo "var3 = '$var3'"
echo "var4 = '$var4'"
echo "var5 = '$var5'"

在这个脚本中,我们给五个变量赋值并显示其结果。注意当给定不同个数的数值后,read怎样操作:

[me@linuxbox ~]$ read-multiple
Enter one or more values > a b c d e
var1 = 'a'
var2 = 'b'
var3 = 'c'
var4 = 'd'
var5 = 'e'
[me@linuxbox ~]$ read-multiple
Enter one or more values > a
var1 = 'a'
var2 = ''
var3 = ''
var4 = ''
var5 = ''
[me@linuxbox ~]$ read-multiple
Enter one or more values > a b c d e f g
var1 = 'a'
var2 = 'b'
var3 = 'c'
var4 = 'd'
var5 = 'e f g'

如果read 命令接受到变量值数目少于期望的数字,那么额外的变量值为空,而多余的输入数据则会被包含到最后一个变量中。如果read 命令之后没有列出变量名,则一个shell 变量,REPLY,将会包含所有的输入:
#!/bin/bash
# read-single: read multiple values into default variable
echo -n "Enter one or more values > "
read
echo "REPLY = '$REPLY'"
这个脚本的输出结果是:
[me@linuxbox ~]$ read-single
Enter one or more values > a b c d
REPLY = 'a b c d'

选项
read 支持以下选送:
选项                     说明
-a array              把输入赋值到数组array 中,从索引号零开始。
-d delimiter         用字符串delimiter 中的第一个字符指示输入结束,而不是一个换行符。
-e                       使用Readline 来处理输入。这使得与命令行相同的方式编辑输入。
-n num               读取num 个输入字符,而不是整行。
-p prompt           为输入显示提示信息,使用字符串prompt。
-r Raw mode.     不把反斜杠字符解释为转义字符。
-s Silent mode.  不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这会很有帮助。
-t seconds          超时. 几秒钟后终止输入。read 会返回一个非零退出状态,若输入超时。
-u fd                   使用文件描述符fd 中的输入,而不是标准输入。

使用各种各样的选项,我们能用read 完成有趣的事情。例如,通过-p 选项,我们能够提供提示信息:
#!/bin/bash
# read-single: read multiple values into default variable
read -p "Enter one or more values > "
echo "REPLY = '$REPLY'"
通过-t 和-s 选项,我们可以编写一个这样的脚本,读取“秘密”输入,并且如果在特定的时间内输入没有完成,就终止输入。
#!/bin/bash
# read-secret: input a secret pass phrase
if read -t 10 -sp "Enter secret pass phrase > " secret_pass; then
            echo -e "\nSecret pass phrase = '$secret_pass'"
else
            echo -e "\nInput timed out" >&2
exit 1
if

这个脚本提示用户输入一个密码,并等待输入10 秒钟。如果在特定的时间内没有完成输入,则脚本会退出并返回一个错误。因为包含了一个-s 选项,所以输入的密码不会出现在屏幕上。

IFS

通常,shell 对提供给read 的输入按照单词进行分离。正如我们所见到的,这意味着多个由一个或几个空格分离开的单词在输入行中变成独立的个体,并被read 赋值给单独的变量。这种行为由shell 变量IFS (内部字符分隔符)配置。IFS 的默认值包含一个空格,一个tab,和一个换行符,每一个都会把字段分割开。我们可以调整IFS 的值来控制输入字段的分离。例如,这个/etc/passwd 文件包含的数据行使用冒号作为字段分隔符。通过把IFS 的值更改为单个冒号,我们可以使用read 读取/etc/passwd 中的内容,并成功地把字段分给不同的变量。这个就是做这样的事情:
#!/bin/bash
# read-ifs: read fields from a file
FILE=/etc/passwd
read -p "Enter a user name > " 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 的值,然后赋给一个新值,再执行read 命令,最后把IFS 恢复原值。显然,完成相同的任务,在命令之前放置变量名赋值是一种更简明的方式。
这个<<< 操作符指示一个here 字符串。一个here 字符串就像一个here 文档,只是比较简短,由单个字符串组成。在这个例子中,来自/etc/passwd 文件的数据发送给read 命令的标准输入。我们可能想知道为什么选择这种相当晦涩的方法而不是:
echo "$file_info" | IFS=":" read user pw uid gid name home shell

你不能管道read
虽然通常read 命令接受标准输入,但是你不能这样做:
echo “foo” | read
我们期望这个命令能生效,但是它不能。这个命令将显示成功,但是REPLY变量总是为空。为什么会这样?
答案与shell 处理管道线的方式有关系。在bash(和其它shells,例如sh)中,管道线会创建子shell。它们是shell 的副本,且用来执行命令的环境变量在管道线
中。上面示例中,read 命令将在子shell 中执行。在类Unix 的系统中,子shell 执行的时候,会为进程创建父环境的副本。当进程结束之后,环境副本就会被破坏掉。这意味着一个子shell 永远不能改变父进程的环境。read 赋值变量,然后会变为环境的一部分。在上面的例子中,read 在它的子shell 环境中,把foo 赋值给变量REPLY,但是当命令退出后,子shell 和它的环境将被破坏掉,这样赋值的影响就会消失。使用here 字符串是解决此问题的一种方法。

菜单

一种常见的交互类型称为菜单驱动。在菜单驱动程序中,呈现给用户一系列选择,并要求用户选择一项。例如,我们可以想象一个展示以下信息的程序:
Please Select:
1.Display System Information
2.Display Disk Space
3.Display Home Space Utilization
0.Quit
Enter selection [0-3] >
使用我们从编写sys info page 程序中所学到的知识,我们能够构建一个菜单驱动程序来执行上述菜单中的任务:

#!/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
         0. 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"
          uptime
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被用来阻止脚本执行不必要的代码。通常在程序中出现多个exit 代码是一个坏想法(它使程序逻辑较难理解),但是它在这个脚本中起作用。


1 0