第14章:处理用户输入

[TOC]


#1. 命令行参数

向shell脚本传递数据的最基本方法是使用命令行参数。命令行参数允许在运行脚本时向命令行添加数据。

1.1 读取参数

bash shell会将一些称为位置参数(positional parameter)的特殊变量分配给输入到命令行中的所有参数。这也包括shell所执行的脚本名称。位置参数变量是标准的数字:1是第一个参数,9。

#!/bin/bash
# Using one command line parameter.
#
echo "Running factorial program $0"
factorial=1
for (( number = 1; number <= $1; number++))
do
    factorial=$[ $factorial * $number ]
done
echo "The factorial of $1 is $factorial."

 ./test1_1.sh 5
Running factorial program ./test1_1.sh
The factorial of 5 is 120.

1.2 读取脚本名

可以用$0参数获取shell在命令行启动的脚本名。

#!/bin/bash
# Testing the $0 parameter
#
echo "The zero parameter is set to:$0"

./test1_2.sh
The zero parameter is set to:./test1_2.sh

./chapter14_user_input/test1_2.sh
The zero parameter is set to:./chapter14_user_input/test1_2.sh

这里存在一个潜在的问题。如果使用另一个命令来运行shell脚本,命令会和脚本名混在一起,出现在0变量的实际字符串不仅仅是脚本名,而是完整的脚本路径时,变量$0就会使用整个路径。通过使用basename命令可以解决上述问题。

#!/bin/bash
# Using basename with the $0 parameter
#
name=$(basename $0)
echo "The script name is: $name"

./chapter14_user_input/test1_2a.sh
The script name is: test1_2a.sh

#2. 特殊参数变量

在bash shell中有些特殊变量,它们会记录命令行参数。

2.1 参数统计

bash shell提供了特殊变量$#来表示脚本运行时携带的命令行参数的个数。可以在脚本中任何地方使用这个特殊变量,就跟普通变量一样。

#!/bin/bash
# Getting the number of command parameters
#
echo "There were $# params supplied."

 ./test2_1.sh
There were 0 params supplied.
./test2_1.sh 1 2 3 4 5
There were 5 params supplied.

2.2 抓取所有的数据

有时候需要抓取命令行上提供的所有参数。@可以用来访问所有的参数。这两个变量都能够在单个变量中存储所有的命令行参数。

***变量会将命令行上提供的所有参数当作一个单词保存。这个单词包含了命令行中出现的每一个参数值。基本上***变量会将这些参数视为一个整体,而不是多个个体。

$@变量会将命令行上提供的所有参数当作同一字符串中的多个独立的单词。这样你能够遍历所有的参数值,得到每个参数。

#!/bin/bash
# Testing $* and $@
#
echo
count=1
#
for param in "$*"
do
    echo "\$* parameter #$count = $param."
    count=$[ $count + 1 ]
done
#
echo
count=1
#
for param in "$@"
do
    echo "\$@ parameter #$count = $param."
    count=$[ $count + 1 ]
done

 ./test2_2a.sh rich barbara katie jessicia

$* parameter #1 = rich barbara katie jessicia.

$@ parameter #1 = rich.
$@ parameter #2 = barbara.
$@ parameter #3 = katie.
$@ parameter #4 = jessicia.

@变量会单独处理每个参数。

#3. 移动变量

bash shell提供了shift命令能够用来操作命令行参数。shift命令会根据它们的相对位置来移动命令行参数。在使用shift命令时,默认情况下它会将每个参数变量向左移动一个位置。所以,变量2中,变量1中,而变量0的值,也就是程序名,不会改变)。

#!/bin/bash
# Demostrating the shift command
#
echo
count=1
while [ -n "$1" ]
do
    echo "Parameter #$count = $1."
    count=$[ $count + 1]
    shift
done

 ./test3.sh rich barbara katie jessica

Parameter #1 = rich.
Parameter #2 = barbara.
Parameter #3 = katie.
Parameter #4 = jessica.

这个脚本通过测试第一个参数值的长度执行了一个while循环。当第一个参数的长度为零时,循环结束。测试完第一个参数后,shift命令会将所有参数的位置移动一个位置。

#4. 处理选项

选项是跟在单破折线后面的单个字母,它能改变命令的行为。bash shell提供了几种处理选项的方法。

4.1 查找选项

表面上看,命令行选择也没什么特殊的。在命令行上,它们紧跟在脚本之后,就跟命令行参数一样。实际上,可以像处理命令行参数一样处理命令行选项。

  1. 处理简单选项
#!/bin/bash
# Extracting command line options as parameters
#
echo
while [ -n "$1" ]
do
    case "$1" in
        -a) echo "Found the -a option.";;
        -b) echo "Found the -b option.";;
        -c) echo "Found the -c option.";;
        -*) echo "$1 is not an option.";;
    esac
    shift
done

./test4_1_1.sh -a -b -c -d

Found the -a option.
Found the -b option.
Found the -c option.
-d is not an option.

case语句会检查每个参数是不是有效选项。如果是的话,就运行对应case语句中的命令。

  1. 分离参数和选项

Linux中通过使用特殊字符(--)来将选项和参数分开,该字符会告诉脚本何时选项结束以及普通参数何时开始。shell会用双破折线来表明选项列表结束。在双破折线之后,脚本就可以放心地将剩下的命令行参数当作参数,而不是选项来处理。

#!/bin/bash
# Extracting options and parameters
#
echo
while [ -n "$1" ]
do
    case "$1" in
        -a) echo "Found the -a option.";;
        -b) echo "Found the -b option.";;
        -c) echo "Found the -c option.";;
        --) shift
            break;;
        *) echo "$1 is not an option.";;
    esac
    shift
done
#
count=1
for param in $@
do
    echo "parameter #$count: $param."
    count=$[ $count + 1 ]
done

在遇到双破折线时,脚本用break命令来跳出while循环。由于过早地跳出了循环,我们需要再加一条shift命令来将破折线移出参数变量。

对于第一个测试,试试用一组普通的选项和参数来运行这个脚本。

./test4_2_1.sh -c -a -b test1 test2 test3

Found the -c option.
Found the -a option.
Found the -b option.
test1 is not an option.
test2 is not an option.
test3 is not an option.

结果说明在处理时脚本认为所有的命令行参数都是选项。接下来,使用双破折线来将命令行上的选项和参数划分开来。

./test4_2_1.sh -c -a -b -- test1 test2 test3

Found the -c option.
Found the -a option.
Found the -b option.
parameter #1: test1.
parameter #2: test2.
parameter #3: test3.

当脚本遇到双破折线时,它会停止处理选项,并将剩下的参数都当作命令行参数。

  1. 处理带值的选项

有些选项会带上一个额外的参数值。在这种情况下,命令行看起来像下面这样。

./test4_2_1.sh -a test1 -b -c -d test2

当命令行选项要求额外的参数时,脚本必须能检测到并正确处理。

#!/bin/bash
# Extracting command line options and values
#
echo
while [ -n "$1" ]
do
    case "$1" in
        -a) echo "Found the -a option.";;
        -b) param="$2"
            echo "Found the -b option, with parameter value $param."
            shift;;
        -c) echo "Found the -c option.";;
        --) shift
            break;;
        *) echo "$1 is not an option";;
    esac
    shift
done
#
count=1
for param in "$@"
do
    echo "Parameter #$count: $param."
    count=$[ $count + 1 ]
done

./test4_1_3.sh -a -b test1 -d

Found the -a option.
Found the -b option, with parameter value test1.
-d is not an option

4.2 使用getopt命令

getopt命令是一个在处理命令行选项和参数时非常方便的工具。它能够识别命令行参数,从而在脚本中解析它们时更方便。

  1. 命令的格式

getopt命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转换成适当的格式。它的命令格式如下:

getopt optstring parameters

optstring是这个过程的关键所在。它定义了命令行有效的选项字母,还定义了哪些选项字母需要参数值。首先,在optstring中列出你要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后加一个冒号。getopt命令会基于你定义的optstring解析提供的参数。

getopt ab:cd -a -b test1 -cd test2 test3
 -a -b test1 -c -d -- test2 test3

optstring定义了四个有效选项字母:a、b、c和d。冒号(:)被放在了字母b后面,因为b选项需要一个参数值。当getopt命令运行时,它会检查提供的参数列表,并基于提供的optstring进行解析。注意,它会自动将-cd选项分成两个单独的选项,并插入双破折线来分隔行中的额外参数。

  1. 在脚本中使用getopt

可以在脚本中使用getopt来格式化脚本所携带的任何命令行选项或参数,但用起来略微复杂。方法是用getopt命令生成的格式化后的版本来替换已有的命令行选项和参数。用set命令能够做到。set命令的选项之一是双破折线(--),它会将命令行参数替换成set命令的命令行值。然后,该方法会将原始脚本的命令行参数传给getopt命令,之后再将getopt命令的输出传给set命令,用getopt格式化后的命令行参数来替换原始的命令行参数,看起来如下所示:

set -- $(getopt -q ab:cd "$@")

现在原始的命令行参数变量的值会被getopt命令的输出替换,而getopt已经为我们格式化好了命令行参数。

#!/bin/bash
# Extract command line options&values with getopt
#
set -- $(getopt -q ab:cd "$@")
#
while [ -n "$1" ]
do
    case "$1" in
        -a) echo "Found the -a option.";;
        -b) param="$2"
            echo "Found the -b option, with parameter value $param."
            shift;;
        -c) echo "Found the -c option.";;
        --) shift
            break;;
        *) echo "$1 is not an option.";;
    esac
    shift
done
#
count=1
for param in "$@"
do
    echo "Parameter #$count: $param."
    count=$[ $count + 1 ]
done

./test4_2_2.sh -a -b test1 -cd test2 test3 test4
Found the -a option.
Found the -b option, with parameter value 'test1'.
Found the -c option.
-d is not an option.
Parameter #1: 'test2'.
Parameter #2: 'test3'.
Parameter #3: 'test4'.

4.3 使用更高级的getopts

与getopt不同,前者将命令行上选项和参数处理后只生成一个输出,而getopts命令一次只处理命令行上检测到的一个参数。处理完所有的参数后,它会退出并返回一个大于0的退出状态码。getopts命令的格式如下:

getopts optstring variable

optstring值类似于getopt命令中的那个。有效的选项字母都会列在optstring中,如果选项字母要求有个参数值,就加一个冒号。要去掉错误消息的话,可以在optstring之前加一个冒号。getopts命令将当前参数保存在命令行中定义的variable中。

getopts命令会用到两个环境变量。如果选项需要跟一个参数值,OPTARG环境变量就会保存这个值。OPTIND环境变量保存了参数列表中getopts正在处理的参数位置。

#!/bin/bash
# Simple demostration of the getopts command
#
while getopts :ab:c opt
do
    case "$opt" in
        a) echo "Found the -a option.";;
        b) echo "Found the -b option, with value $OPTARG";;
        c) echo "Found the -c option";;
        *) echo "Unknown option: $opt";;
    esac
done

 ./test4_3_1.sh -ab test1 -c
Found the -a option.
Found the -b option, with value test1
Found the -c option

getopts命令知道何时停止处理选项,并将参数留给你处理。在getopts处理每个选项时,它会将OPTIND环境变量值增一。在getopts完成处理时,你可以使用shift命令和OPTIND值来移动参数。

#!/bin/bash
# Processing options¶meters with getopts
#
while getopts :ab:cd opt
do
    case "$opt" in
        a) echo "Found the -a option.";;
        b) echo "Found the -b option, with value $OPTARG.";;
        c) echo "Found the -c option.";;
        d) echo "Found the -d option.";;
        *) echo "Unknown option: $opt.";;
    esac
done
#
shift $[ $OPTIND - 1 ]
#
echo
count=1
for param in "$@"
do
    echo "Parameter #$count: $param."
    count=$[ $count + 1 ]
done

./test4_3_2.sh -a -b test1 -d test2 test3 test4
Found the -a option.
Found the -b option, with value test1.
Found the -d option.

Parameter #1: test2.
Parameter #2: test3.
Parameter #3: test4.

#5. 将选项标准化

在创建shell脚本时,显然可以控制具体怎么做。你完全可以决定用哪些字母选项以及它们的用法。但有些字母选项在Linux世界里已经有了某种程度的标准含义。

选项 描述
-a 显示所有对象
-c 生成一个计数
-d 指定一个目录
-e 扩展一个对象
-f 指定读入数据的文件
-h 显示命令的帮助信息
-i 忽略文本大小写
-l 产生输出的长格式版本
-n 使用非交互模式
-o 将所有输出重定向到指定的输出文件
-q 以安静模式运行
-r 递归地处理目录和文件
-s 以安静模式运行
-v 生成详细输出
-x 排除某个对象
-y 对所有问题回答yes

#6. 获得用户输入

尽管命令行选项和参数是从脚本用户处获得输入的一种重要方式,但有时脚本的交互性还需要更强一些。bash shell提供了read命令来实现强交互命令输入。

6.1 基本的读取

read命令从标准输入(键盘)或另一个文件描述符中接受输入。在收到输入后,read命令会将数据放进一个变量。

#!/bin/bash
# Testing the read command
#
echo -n "Enter your name: "
read name
echo "Hello $name, welcome to my program."

echo命令使用了-n选项。该选项不会在字符串末尾输出换行符,允许脚本用户紧跟其后输入数据,而不是下一行。

6.2 超时

使用read命令时,脚本很可能会一直苦等这脚本用户的输入。如果不管是否有数据输入,脚本都必须继续执行,你可以用-t选项来指定一个计时器。-t选项指定了read命令等待输入的秒数。当计数器过期后,read命令会返回一个非零退出状态码。

#!/bin/bash
# Timing the date entry
#
if read -t 5 -p "Please enter your name: " name
then
    echo "Hello $name, welcome to my script."
else
    echo "Sorry, too slow!"
fi

 ./test6_2.sh
Please enter your name: Sorry, too slow!

read命令包含了-p选项,允许你直接在read命令行指定提示符。

6.3 隐藏方式读取

有时你需要从脚本用户处得到输入,但又在屏幕上显示输入信息。其中典型的例子就是输入的密码,但除此之外还有很多其他需要隐藏的数据类型。-s选项可以避免在read命令中输入的数据出现在显示器上(实际上,数据会被显示,只是read命令会将文本颜色设成跟背景色一样)。

#!/bin/bash
# Hiding input data from the monitor
#
read -s -p "Enter your password: " pass
echo
echo "Is your password really $pass? "

输入提示符输入的数据不会出现在屏幕上,但会赋给变量,以便在脚本中使用。

6.4 从文件读取

最后,也可以用read命令在读取Linux系统上文件里保存的数据。每次调用read命令,它都会从文件中读取一行文本。当文件中再没有内容时,read命令会退出并返回非零退出状态码。其中最难的部分是将文件中的数据传给read命令。最常见的方法是对文件使用cat命令,将结果通过管道直接传给含有read命令的while命令。

#!/bin/bash
# Reading data from a file
#
count=1
cat read.txt | while read line
do
    echo "Line $count: $line"
    count=$[ $count + 1 ]
done
echo "Finished processing the file."

cat read.txt
The quick brown dog jumps over the lazy fox.
This is a test, this is only a test.
O Romeo, Romeo! Wherefore art thou Remeo?

./test6_4.sh
Line 1: The quick brown dog jumps over the lazy fox.
Line 2: This is a test, this is only a test.
Line 3: O Romeo, Romeo! Wherefore art thou Remeo?
Finished processing the file.

while循环会持续通过read命令处理文件中的行,直到read命令以非零退出状态码退出。

你可能感兴趣的:(第14章:处理用户输入)