Shell学习笔记(4)——处理用户输入

命令行参数

脚本还可以接受一些参数。向shell脚本传递数据的最基本方法是使用命令行参数,命令行参数就像是一些常规的Linux命令一样,例如head -20 file001.sh,在一条命令上,head命令就能接收一个-20的参数,用户自己定义的脚本,也可以接收一定的参数。

读取参数

第1个案例:读取1个参数

bash shell会将一些称为位置参数(positional parameter)的特殊变量分配给输入到命令行中的所有参数,这也包括shell所执行的脚本名称,位于参数变量是标准的数据,其中,$0是程序名,$1是第1个参数,$2是第2个参数,直到第9个参数,即$9。下面的案例是shell脚本中使用单个命令行参数的例子。

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
# using one command line parameter
#
factorial=1
for (( number=1; number <= $1;number++ ))
# 这里的$1指的是脚本运行过程中输入的参数,在后面的运行过程中,输入5
do
factorial=$[ $factorial*$number ]
done
echo The factorial of $1 is $factorial

运行结果如下所示:

1
2
biotest@ubuntu:~/userdata$ bash test1.sh 5
The factorial of 5 is 120

运行过程:第1次:factorial=1,number=1;第2次:n=2,f=2;第3次:n=3,f=6;第4次:n=4,f=24;第5次:n=5;f=120。

第2个案例:读取2个及以上参数

如果要读取更多的参数,需要将每个参数用空格分开,如下所示:

1
2
3
4
5
6
7
#!/bin/bash
# testing two command line parameters
#
total=$[ $1*$2 ]
echo The first parameter is $1.
echo The second parameter is $2.
echo The total value is $total.

运行结果如下所示:

1
2
3
The first parameter is 2.
The second parameter is 5.
The total value is 10.

第3个案例,使用字符串作为变量

1
2
3
4
#!bin/bash
# testing string parameters
#
echo Hello $1, glad to meet you.

运行结果如下所示:

1
2
biotest@ubuntu:~/userdata$ bash test3.sh Zhang
Hello Zhang, glad to meet you.

在这个案例中,使用了字符串作为脚本的参数,但是,如果参数中含有空格,则会出现问题,如下所示:

1
2
biotest@ubuntu:~/userdata$ bash test3.sh Zhang San
Hello Zhang, glad to meet you.

脚本只识别了Zhang,没有识别出完整的Zhang San,因为shell脚本会把空格分隔开的字符串当成两个参数,如果要使含有空格的字符串当成一个,需要添加引号,如下所示;

1
2
biotest@ubuntu:~/userdata$ bash test3.sh 'Zhang San'
Hello Zhang San, glad to meet you.

第4个案例:参数大于9个

如果脚本需要的命令行参数大于9个,此时需要在变量数字周围加上花括号,例如${``10``},如下所示:

1
2
3
4
5
6
7
#!/bin/bash
# handling lots of paramerters
#
total=$[ ${10}*${11} ]
echo The tenth parameter is ${10}
echo The eleventh parameter is ${11}
echo The total is $total

结果如下所示:

1
2
3
4
biotest@ubuntu:~/userdata$ bash test4.sh 1 2 3 4 5 6 7 8 9 10 11 12
The tenth parameter is 10
The eleventh parameter is 11
The total is 110

第5个案例:读取脚本名

$0参数的意义。

$0参数用于获取shell在命令行启动的脚本,如下所示:

1
2
3
4
#!/bin/bash
# Testing the $0 parameter
#
echo The zero parameter is set to:$0

需要注意的是,如果使用完整路径运行脚本,则$0就是完整脚本,如下所示:

1
2
biotest@ubuntu:~/userdata$ bash /home/biotest/userdata/test5.sh
The zero parameter is set to:/home/biotest/userdata/test5.sh
basename命令可以补充$0参数的局限

如果要写一个根据脚本名来执行不同功能的脚本,则就需要注意把脚本的运行路径给剥离掉,shell中有一个命令可以考虑,即basename命令,如下所示:

1
2
3
4
5
6
#!/bin/bash
# Using basename with the $0 parameter
#
name=$(basename $0)
echo
echo The script name is: $name

运行结果如下所示:

1
2
3
4
biotest@ubuntu:~/userdata$ bash test5b.sh
The script name is: test5b.sh
biotest@ubuntu:~/userdata$ bash /home/biotest/userdata/test5b.sh
The script name is: test5b.sh

从结果可以看出来,使用了basename命令后,即使运行脚本时输入全路径,$0也只是脚本的名称。

相对路径与绝对路径的案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
# Testing a Multi-function script
#
name=$(basename $0)
#
if [ $name = "addem" ]
then
total=$[ $1 + $2 ]
#
elif [ $name = "multem" ]
then
total=$[ $1*$2 ]
fi
#
echo
echo The calculated value is $total
#

运行结果如下所示:

1
2
3
4
5
6
7
8
9
10
biotest@ubuntu:~/userdata$ cp test6.sh addem
biotest@ubuntu:~/userdata$ chmod u+x addem
biotest@ubuntu:~/userdata$ ln -s test6.sh multem
biotest@ubuntu:~/userdata$ ls -l *em
-rwxrw-r-- 1 biotest biotest 224 May 1 10:48 addem
lrwxrwxrwx 1 biotest biotest 8 May 1 10:48 multem -> test6.sh
biotest@ubuntu:~/userdata$ bash addem 2 5
The calculated value is 7
biotest@ubuntu:~/userdata$ sudo bash ./multem 2 5
The calculated value is 10

结果解释:第一,cp test6.sh addem复制test6.sh脚本,新生成的文件命令为addem;第二,chmod u+x addem,将addem文件权限更改为可执行;第三,ln -s test6.sh multem,其中ln是为文件创建链接,-s表示软连接,可以理解为Linux的快捷方式;第四,当执行addem 2 5时,执行total=$[ $1 + $2 ]语句,如果是bash ./multem 2 5,则执行total=$[ 41*$2 ]语句。

测试参数

当脚本认为参数变量中会有数据,而实际上没有时,脚本就有可能会产生错误消息。通常要避免这种脚本的写法,在使用参数前一定要检查其中是否存在为数据,如下所示:

1
2
3
4
5
6
7
8
9
10
biotest@ubuntu:~/userdata$ cat test7.sh
#!/bin/bash
# testing parameters befoire user
#
if [ -n "$1" ]
then
echo Hello $1, gload to meet you.
else
echo "Sorry you did not identify yourself."
fi

运行结果如下所示:

1
2
3
4
biotest@ubuntu:~/userdata$ bash test7.sh Zhang
Hello Zhang, gload to meet you.
biotest@ubuntu:~/userdata$ bash test7.sh
Sorry you did not identify yourself.

代码解释:在if语句中使用了if [ -n "$1" ]语句,这里的-n是用于判断$1中是否有数据。

特殊参数变量

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

参数统计基本用法

bash shell有一个特殊的变量,可以记录命令行中输入了多少个参数,其中这个特殊变量就是$#,哦可以肝脚本中任何地方使用这个特殊的变量,如下所示:

1
2
3
4
#!/bin/bash
# getting the number of parameters
#
echo There were $# parameters supplied.

运行结果如下所示:

1
2
3
4
5
6
biotest@ubuntu:~/userdata$ bash test8.sh
There were 0 parameters supplied.
biotest@ubuntu:~/userdata$ bash test8.sh 1 2 3 4 5 6 7
There were 7 parameters supplied.
biotest@ubuntu:~/userdata$ bash test8.sh "Zhang San"
There were 1 parameters supplied.

参数统计案例

1
2
3
4
5
6
7
8
9
10
11
12
13
#/bin/bash
# Testing parameters
#
if [ $# -ne 2 ] # -ne测试命令行参数的数量,-ne是检查$#是否不等于2,ne相当于non-equal
then
echo
echo Usage: test9..sh a b
echo
else
total=$[ $1 + $2 ]
echo
echo The total is $total
echo

结果运行如下所示:

1
2
3
4
5
6
biotest@ubuntu:~/userdata$ bash test9.sh
Usage: test9..sh a b
biotest@ubuntu:~/userdata$ bash test9.sh 10
Usage: test9..sh a b
biotest@ubuntu:~/userdata$ bash test9.sh 10 10 15
Usage: test9..sh a b

需要注意的是,如果认为$#变量含有参数的总数,而变量$\{$#\}代表了最后一个命令行参数变量,那就会出错,如下所示:

1
2
3
4
#!/bin/bash
# testing grabbing last parameter
#
echo The last parameter was ${$#}

运行结果如下所示:

1
2
biotest@ubuntu:~/userdata$ bash badtest1.sh 10
The last parameter was 19343

结果是19343,这说明,在花括号内不能使用美元符号,除非按照下面的方式进行修改,如下所示:

1
2
3
4
5
6
7
8
#!/bin/bash
# Grabbing the last parameter
#
params=$#
echo
echo The last parameter is $params
echo The last parameter is ${!#}
echo

运行结果如下所示:

1
2
3
4
5
6
7
biotest@ubuntu:~/userdata$ bash test10.sh 1 2 3 4 5 6
The last parameter is 6
The last parameter is 6
biotest@ubuntu:~/userdata$ bash test10.sh
The last parameter is 0
The last parameter is test10.sh

从结果来看,如果将$#变量的值赋给了变量params,然后也按特殊命令行参数变量的格式(即使用$/{!#/})使用了该变量,结果就能正常显示。但是,当脚本没有参数时,$#的值为0,params变量的值也一样,但$\{!#\}变量的返回值则是脚本名本身。

抓取所有的数据

有时候需要抓取命令行上提供的所有参数,这时候不需要先用$#变量来判断命令行上有多少参数,然后再进行遍历,可以使用其他的特殊变量来解决这个问题,其中$*$@变量可以访问所有的参数,这两个变量都能够在单个变量中存储所有的命令行参数。

$*变量会将命令行上提供的所有参数当作一个单词保存,这个单词包含了命令行中出现的每一个参数,基本上$*变量会将这些参数视为一个整体,而不是单独的个体。而$@变量会将命令行上提供的所有参数当作同一字符串中多个独立的单词,这样可以遍历所有的参数值,得到每个参数,这通常使用for命令完成。如下所示:

1
2
3
4
5
6
7
#!/bin/bash
# testing $* and $@
#
echo
echo "Using the \$* method: $*"
echo
echo "Using the \$@ mehtods: $@"

运行结果如下所示:

1
2
3
biostudy@ubuntu18:~/script$ bash test11.sh Zhang Li Wang
Using the $* method: Zhang Li Wang
Using the $@ mehtods: Zhang Li Wang

从结果来看,$*$@在形式上并没有区别。但是下面的例子则能看出区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
# testing $* and $@
#
echo
count=1
for param in "$*"
do
echo "\$ Parameter # $count=$param"
count=$[ $count+1 ]
done
count=1
for param in "$@"
do
echo "\$@ Parameter # $count=$param"
ecount=$[ $count+1 ]
done

运行结果如下所示:

1
2
3
4
5
6
biotest@ubuntu:~/userdata$ bash test12.sh Zhang Li Wang
$ Parameter # 1=Zhang Li Wang
$@ Parameter # 1=Zhang
$@ Parameter # 1=Li
$@ Parameter # 1=Wang

从结果可以看出来,使用for遍历这两个特殊的变量时,$*会将所有参数当成单个参数,而$@变量会单独处理所有的参数。

移动变量

bash shell中的shift命令可以操作命令行参数,shift可以根据它们的相对位置来移动命令行参数。当使用该命令时,它会将每个参数变量向左移动一个位置。因此,变量$3的值会移到$2,变量$2的值会移到$1,而变量$1的值则会被删除(因为$0的值是程序名,不会改变)。这是遍历命令行参数的一个方法,当用户不矢科有多少个参数时,可以只操作第1个参数,移动参数,然后继续操作下一个参数。看一个案例:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
# demonstrating the shift command
echo
count=1
while [ -n "$1" ]
do
echo "Parameter #$count=$1"
count=$[ $count+1 ]
shift
done

运行结果如所示:

1
2
3
4
5
6
[000.DESKTOP-FS88S5S] > bash test13.sh Zhang Li Wang Tian
Parameter #1=Zhang
Parameter #2=Li
Parameter #3=Wang
Parameter #4=Tian

代码及结果解释: -n 测试来检查命令行参数$1中是否有数据。如果不是0,就执行,一直执行到$1参数为0为止。也可以为shift提供一个参数,指明要移动的位置数即可,如下所示:

1
2
3
4
5
6
#!/bin/bash
# demonstrating a multi-positon shift
#
echo "The original parameters: $*"
shift 2
echo "Here's the new first parameter: $1"

运行结果如下所示:

1
2
3
[000.DESKTOP-FS88S5S] > bash test14.sh 1 2 3 4 5
The original parameters: 1 2 3 4 5
Here's the new first parameter: 3

从上述案例可知,通过使用shfit的参数可以跳过不想要的参数。

处理选项

选项指的是这样的一种情况,在命令后面的短横线的单个字母 ,例如 ls -a,其中-a就是选项。

查找选项

在提取每个单独参数时用case语句来判断某个参数是否为选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/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

运行结果如下所示:

1
2
3
4
5
biotest@ubuntu:~/userdata$ bash test15.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
2
3
4
biotest@ubuntu:~/userdata$ bash test15.sh -d -c -a
-d is not an option
Found the -c option
Found the -a option

case语句在命令行参数中找到一个选项,就处理一个选项,如果命令行上的还提供了其他参数,就可以在case语句的通用情况处理部分中处理。

分离参数和选项

在shell脚本中同时使用选项有参数时,Linux处理这种问题的标准方式是用特殊字符,也就是双破折号(--)将二者分开,该字符会告诉脚本何时选项结束以及普通参数何时开始。在双破折线之后,脚本可以将剩下的命令行参数当作参数,而不是选项,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/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

运行结果如下所示:

1
2
3
4
5
6
7
biotest@ubuntu:~/userdata$ bash test16.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

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

处理带值的选项

有些选项会带上额外的参数值,例如像这样的bash testing.sh -a test1 -b -c -d test2,如果例行选项中带有额外的参数,脚本必须要能检测到,并且进行处理,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/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

运行结果如下所示:

1
2
3
4
5
biotest@ubuntu:~/userdata$ bash test17.sh -a -b test1 -d
Found the -a option
Found the -b option, with parameter value test1
-d is not an option

代码及结果解释:在这案例中,case语句定义了3个安要处理的选项,其中-b选项还需要一个额外的参数值,由于要处理的参数是$1,额外的参数值就应该位于$2位置上(因为所有的参数在处理完后就会被移出),只要将参数值从$2变量中提取出来就行,因为这个选项占了2个参数位,因此还需要shift命令多移动一个位置。只用这些基本的特性,整个过程就能正常工作,不管按什么顺序放置选项(但要记住包含每个选项相应的选项参数)。如下所示:

1
2
3
4
biotest@ubuntu:~/userdata$ bash test17.sh -b test1 -a -d
Found the -b option, with parameter value test1
Found the -a option
-d is not an option

此时,shell脚本就有了处理例行选项的基本能力,但还一些限制,例如,想要多个选项放一个参数时,就像ls -lh这个命令这样,脚本就无法正常工作,如下所示:

1
2
biotest@ubuntu:~/userdata$ bash test17.sh -ac
-ac is not an option

此时需要getopt命令。

使用getopt命令

getopt命令基本用法

getopt命令是一个在处理命令行选项和参数时非常方便的工具,它能够识别命令行参数,从而在脚本中解析它们时更方便。getopt命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转换为适当的格式,它的使用方法为getopt optstring parameters,其中,optstring定义了命令行有效的选项字符,不这它义了哪些选项字母需要参数值。首先在optstring中列出要在脚本中用到的每个命令行选项字母,然后在每个需要参数值的选项字母后面加一个冒号,getopt命令会基于定义的optstring解析提供的参数。如下所示:

1
2
biotest@ubuntu:~/userdata$ getopt ab:cd -a -b test1 -cd test2 test3
-a -b test1 -c -d -- test2 test3

optstring定义了4个有效选项字母,a、b、c和d。冒号()被放到了字母b的后面,因为b选项需要一个参数值,当getopt命令运行时,它会检查提供的参数列表(-a -b test1 -cd test2 test3),并基于提供的optstring进行解析。此时,它会自动将-cd选项分成两个单独的选项,并插入双破折线来分隔行中的额外参数,如果指定了一个不在optstring中的选项,默认情况下,getopt会产生一条错误消息,如下所示:

1
2
3
biotest@ubuntu:~/userdata$ getopt ab:cd -a -b test1 -cde test2 test3
getopt: invalid option -- 'e'
-a -b test1 -c -d -- test2 test3

如果需要忽略这条错误消息,可以在命令后加-q选项,如下所示:

1
2
biotest@ubuntu:~/userdata$ getopt -q ab:cd -a -b test1 -cde test2 test3
-a -b 'test1' -c -d -- 'test2' 'test3'

注意:getopt命令选项必须出现在optstring之间。

在脚本中使用getopt

在脚本中使用getopt来格式化脚本所携带的任何选项或参数时,方法就是使用getopt命令生成的格式化后的版本替换已有的命令行选项的参数。这其中要用到set命令。set命令的选项之一就是双破折线(--),它会将命令行参数值的成set命令的命令行值。该方法会将原始脚本的命令行参数传给getopt命令,之后再将getopt命令的输出传给set命令,用getopt格式化后的命令行参数来替换原始的命令行参数,看起来就像这样的set --$(getopt -q ab:cd "$@"),此时,原始命令行参数变量的值会被getopt命令的输出替换,而getopt已经格式化好了命令行参数。看一个案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/bash
# Extract command line options and values with getop
#
set --$(getopt -q ab:cd "$@")
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option";;
-b) param="$2"
echo "Found the -b option, with paraeter 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

运行结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
biotest@ubuntu:~/userdata$ bash test18.sh -ac
Found the -a option
Found the -c option
biotest@ubuntu:~/userdata$ bash test18.sh -a -b test1 -cd test2 test3 test4
Found the -a option
Found the -b option, with paraeter value 'test1'
Found the -c option
-d is not an option
Parameter 1: 'test2'
Parameter 2: 'test3'
Parameter 3: 'test4'
biotest@ubuntu:~/userdata$ bash test18.sh -a -b test1 -cd "test2 test3" test4
Found the -a option
Found the -b option, with paraeter value 'test1'
Found the -c option
-d is not an option
Parameter 1: 'test2
Parameter 2: test3'
Parameter 3: 'test4

从运行结果来看,第一个与第二个没有问题,第三个出现了问题,在第三个运行过程中,参数选项其中人有"test2 test3",用了双引号,由于getopt命令不擅长处理带空格和引号的参数值,它会将空格当作参数分隔符,而不是根据双引号将二者当作一个参数。此时就需要另外一个命令,即getopts,这个命令是在getopt后面加了一个s

使用getopts命令(注意后面加了s)

getopts命令能够与已有的shell参数变量进行配合。每调用它时,它一次只处理命令行上检测到的一个参数,处理完所有的参数后,它会退回一个大于0的退出状态码,因此这个命令很适合解析命令行所有参数的循环。它的用法为getopts optstring variableoptstring中储存的是有效的选项字母,如果选项字母要求有个参数值,就要加一个冒号,要去年错误消息的话,可以在optstring之前加一个冒号,getopts命令将当前参数保存在命令行中定义的variable中。getopts命令会用到两个环境变量,如果选项需要跟一个参数值,OPTARG环境变量会保存这个值,OPTIND环境变量保存了参数列表中getopts正在处理的参数位置。这样能够在处理完选项之后继续处理其他命令行参数。看一个案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# simple demonstration of the getopts command
#
echo
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";;
*) echoi "Unknown options:$opt";;
esac
done

运行结果如下所示:

1
2
3
4
biotest@ubuntu:~/userdata$ bash test19.sh -ab test1 -c
Found the -a option
Found the -b option, with value test1
Found the -c option

while语句定义了getopts命令,指明了要查找哪些命令选项,以及每次迭代中储存它们的变量名(opt)。此时case语句的用法有些不同,getopts命令解析命令行选项时会移除开头的单破拆线,所以在case定义中不用单破折线。getopts命令的参数值中可以包含空格,如下所示:

1
2
Found the -b option, with value test1 test2
Found the -a option

还可以将选项字母和参数值放在一起使用,而不用加空格,如下所示:

1
2
3
4
biotest@ubuntu:~/userdata$ bash test19.sh -abtest1
Found the -a option
Found the -b option, with value test1

getopts命令能够从-b选项中正确解析出test1值。除此之外,getopts还能够将命令行上找到的所有未定义的选项统一输出成问号,如下所示:

1
2
3
4
5
6
7
8
9
biotest@ubuntu:~/userdata$ bash test19.sh -d
Unknown options:?
biotest@ubuntu:~/userdata$ bash test19.sh -acde
Found the -a option
Found the -c option
Unknown options:?
Unknown options:?

optstring中未定义的选项字母会以问号形式发给代码。getopts命令知道何时停止处理选项,并将参数留给用户处理,在使用getopts处理每个选项时,它会将OPTIND环境变量值增一。在getopts完成处理时,用户可以使用shift命令和OPTIND值来移动参数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# Processing options and parameters with getopts
echo
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";;
c) echo "Found the 0d option";;
*) echo "Unknown option: $opt";;
esac
done
shift $[ $OPTIND - 1 ]
echo
count=1
for param in "$@"
do
echo "Parameter $count: $param"
coiunt=$[ $count+1 ]
done

运行结果如下所示:

1
2
3
4
5
6
7
8
9
biotest@ubuntu:~/userdata$ bash test20.sh -a -b test1 -d test2 test3 test4
Found the -a option
Found the -b option, with value test1
Unknown option: d
Parameter 1: test2
Parameter 1: test3
Parameter 1: test4

选项标准化

在Linux系统中,一些字母选项已经拥有了某种程度的标准含义,因此在写bash shell脚本时,用户自己脚本中如果要使用一些选项参数的话,最好与Linux系统中的保持一致,一些常用的linux命令选项如下所示:

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

获得用户输入

虽然命令行选项和参数可以从脚本用户处获得输入,但还有一种情况下,需要读取用户的一些信息,例如在运行某个脚本时,需要用户输入自己的用户名。bash shell为此提供了read命令。

read的基本用法

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

1
2
3
4
5
6
7
#!/bin/bash
# testing the read command
#
echo -n "Enter your name: "
read name
echo "Hello, $name, welcome to my program."
#

运行后结果如下所示:

1
2
3
biotest@ubuntu:~/userdata$ bash test21.sh
Enter your name: Zhang San
Hello, Zhang San, welcome to my program.

代码及结果解释:生成提示的echo使用了-n选项,该选项不会在字符串末尾输出换行符,允许脚本用户紧跟其后输入数据,而不是下一行。read命令还包含了-p选项,可以直接在read命令行指定指示符,如下所示:

1
2
3
4
5
6
7
#!/bin/bash
# testing the read -p option
#
read -p "Please enter your age: " age
days=$[ $age*365 ]
echo "That makes your over $days days old!"
#

运行结果如下所示:

1
2
3
biotest@ubuntu:~/userdata$ bash test22.sh
Please enter your age: 20
That makes your over 7300 days old!

代码及结果解释,在第一个例子中当有名字输入时,read命令会将姓和名保存在同一个变量中,read命令会将提示符后输入的所有数据分配给单个变量,要么用户指定多个变量。输入的每个数据值都会分配给变量列表中的下一个变量,如果变量数量不够,剩下的数据就全部分配给最后一个变量,如下所示:

1
2
3
4
5
#!/bin/bash
# entering multiple varibales
read -p "Enter your name: " first last
echo "Checking data for $last, $first..."

结果运行如下所示:

1
2
3
biotest@ubuntu:~/userdata$ bash test23.sh
Enter your name: Zhang San
Checking data for San, Zhang...

如果read命令行中不指定变量,read命令会将它收到的任何数据都放到特殊环境变量REPLY中。

1
2
3
4
5
6
7
#!/bin/bash
# Testing the REPLY Environemnt variable
#
read -p "Enter uyuour name: "
echo
echo Hello $REPLY, welcome to my program.
#

运行结果如下所示:

1
2
3
4
biotest@ubuntu:~/userdata$ bash test24.sh
Enter uyuour name: Zhang
Hello Zhang, welcome to my program.

REPLY环境变量会保存输入的所有数据,可以在shell脚本中像其他变量一样使用。

超时

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

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
# timing the data entry
#
if read -t 5 -p "Please enter your name: " name
then
echo "Hello $name, welcome to my script"
else
echo
echo "Sorry, too slow!"
fi

运行结果如下所示:

1
2
3
4
5
6
biotest@ubuntu:~/userdata$ bash test25.sh
Please enter your name: Zhang
Hello Zhang, welcome to my script
biotest@ubuntu:~/userdata$ bash test25.sh
Please enter your name:
Sorry, too slow!

代码及结果解释:如果计时器过期,read命令会以非零退出状态码退出,可以使用如if-then语句或while循环这种标准的结构化语句所发生的具体情况,在这个例子中,if语句不成立,shell会执行else部分的命令。也可以不进行诸,让read命令来统计输入的字符数,当输入的字符数达到预设的字符数时,就自动退出,将输入的数据赋给变量,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
# getting just one character of input
#
read -n1 -p "Do you want to continue [Y/N]? " answer
case $answer in
Y|y) echo
echo "fine, continue on...";;
N|n) echo
echo OK, goodbye
exit;;
esac
echo "This is the end of the script"

运行结果如下所示:

1
2
3
4
5
6
7
biotest@ubuntu:~/userdata$ bash test26.sh
Do you want to continue [Y/N]? Y
fine, continue on...
This is the end of the script
biotest@ubuntu:~/userdata$ bash test26.sh
Do you want to continue [Y/N]? n
OK, goodbye

在这个案例中,将-n选项和值1一起使用,告诉read命令在接受单个字符后退出,只要按下单个字符回答后,read命令就会接受输入并将它传给变量,无需按回车键。

隐藏方式读取

有时候需要从脚本用户处得到输入,在屏幕上显示输入信息。但有种例外,就是输入密码,需要隐藏输入的信息。在read命令使用时,使用-s选项可以避免在read命令中输入的数据出现在显示器上(其实数据是会显示的,只是read命令将文本的颜色设成了跟背景色一样),下面是一个案例。

1
2
3
4
5
6
#!/bin/bash
# hiding input data from the monitor
#
read -s -p "Enter your passwd: " pass
echo
echo "Is your password really $pass?"

运行结果如下所示:

1
2
3
biotest@ubuntu:~/userdata$ bash test27.sh
Enter your passwd:
Is your password really dsfdf?

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

从文件中读取数据

read可以用于读取文件中的数据,每次调用read命令时,它就会从文件中读取一行文本。当文件中再没有内容时,read命令就会退出,并返回非零退出状态码。通常使用cat命令将数据中的文件传给含有read命令的while命令,看一个案例。

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
# reading data from a file
count=1
cat test|while read line
do
echo "Line $count: $line"
count=$[ $count+1 ]
done
echo "Finished processing the file"

运行结果如下所示:

1
2
3
4
biotest@ubuntu:~/userdata$ cat test
The quick brown dog jumps over the lazy fox.
This is a test, this is only a test.
O Romeo, Romeo! Wherefore art thou Romeo?

总结