14.1 命令行参数

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

向脚本addem传递了两个命令行参数(10和30)

./addem 10 30

14.1.1 读取参数

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

连接用户的方式(14处理用户输入)(1)

连接用户的方式(14处理用户输入)(2)

参数也可以是文本字符串,要在参数值中包含空格,必须要用引号(单引号或双引号均可)。

如果脚本需要的命令行参数不止9个,你仍然可以处理,但是需要稍微修改一下变量名。在第9个变量之后,你必须在变量数字周围加上花括号,比如${10}。

连接用户的方式(14处理用户输入)(3)

14.1.2 读取脚本名

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

连接用户的方式(14处理用户输入)(4)

当传给$0变量的实际字符串不仅仅是脚本名,而是完整的脚本路径时,变量$0就会使用整个路径。

连接用户的方式(14处理用户输入)(5)

连接用户的方式(14处理用户输入)(6)

basename命令会返回不包含路径的脚本名。

连接用户的方式(14处理用户输入)(7)

在使用参数前一定要检查其中是否存在数据。

连接用户的方式(14处理用户输入)(8)

14.2 特殊参数变量14.2.1 参数统计

特殊变量$#含有脚本运行时携带的命令行参数的个数。

连接用户的方式(14处理用户输入)(9)

14.2.2 抓取所有的数据

$* $@ 变量可以用来轻松访问所有的参数。这两个变量都能够在单个变量中存储所有的命令行参数。

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

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

从表面上看,两个变量产生的是同样的输出,都显示出了所有命令行参数。

连接用户的方式(14处理用户输入)(10)

$*变量会将所有参数当成单个参数,而$@变量会单独处理每个参数。

连接用户的方式(14处理用户输入)(11)

14.3 移动变量

在使用shift命令时,默认情况下它会将每个参数变量向左移动一个位置。所以,变量$3的值会移到$2中,变量$2的值会移到$1中,而变量$1的值则会被删除(注意,变量$0的值,也就是程序名,不会改变)。

连接用户的方式(14处理用户输入)(12)

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

也可以一次性移动多个位置,只需要给shift命令提供一个参数,指明要移动的位置数就行了。

连接用户的方式(14处理用户输入)(13)

14.4 处理选项

选项是跟在单破折线后面的单个字母,它能改变命令的行为。

14.4.1 查找选项1. 处理简单选项

在提取每个单独参数时,用case语句(参见第12章)来判断某个参数是否为选项。

连接用户的方式(14处理用户输入)(14)

2. 分离参数和选项

shell脚本中有同时使用选项和参数的情况,特殊字符双破折线(--)来表明选项列表结束。在双破折线之后,脚本就可以放心地将剩下的命令行参数当作参数,而不是选项来处理了。

连接用户的方式(14处理用户输入)(15)

连接用户的方式(14处理用户输入)(16)

连接用户的方式(14处理用户输入)(17)

3. 处理带值的选项

有些选项会带上一个额外的参数值

连接用户的方式(14处理用户输入)(18)

在这个例子中,case语句定义了三个它要处理的选项。-b选项还需要一个额外的参数值。由于要处理的参数是$1,额外的参数值就应该位于$2(因为所有的参数在处理完之后都会被移出)。只要将参数值从$2变量中提取出来就可以了。当然,因为这个选项占用了两个参数位,所以你还需要使用shift命令多移动一个位置。

14.4.2 使用getopt命令1. 命令的格式

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

getopt optstring parameters

optstring是这个过程的关键所在。它定义了命令行有效的选项字母,还定义了哪些选项字母需要参数值。

首先,在optstring中列出你要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后加一个冒号。getopt命令会基于你定义的optstring解析提供的参数。

连接用户的方式(14处理用户输入)(19)

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

如果指定了一个不在optstring中的选项,默认情况下,getopt命令会产生一条错误消息。

连接用户的方式(14处理用户输入)(20)

如果想忽略这条错误消息,可以在命令后加-q选项。

连接用户的方式(14处理用户输入)(21)

2. 在脚本中使用getopt

连接用户的方式(14处理用户输入)(22)

连接用户的方式(14处理用户输入)(23)

连接用户的方式(14处理用户输入)(24)

14.4.3 使用更高级的getopts

与getopt不同,前者将命令行上选项和参数处理后只生成一个输出,而getopts命令能够和已有的shell参数变量配合默契。

每次调用它时,它一次只处理命令行上检测到的一个参数。处理完所有的参数后,它会退出并返回一个大于0的退出状态码。这让它非常适合用解析命令行所有参数的循环中。

getopts命令的格式如下:

getopts optstring variable

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

getopts命令会用到两个环境变量。如果选项需要跟一个参数值,OPTARG环境变量就会保存这个值。OPTIND环境变量保存了参数列表中getopts正在处理的参数位置。这样你就能在处理完选项之后继续处理其他命令行参数了。

连接用户的方式(14处理用户输入)(25)

可以在参数值中包含空格。

连接用户的方式(14处理用户输入)(26)

将选项字母和参数值放在一起使用,而不用加空格。

连接用户的方式(14处理用户输入)(27)

getopts还能够将命令行上找到的所有未定义的选项统一输出成问号。

连接用户的方式(14处理用户输入)(28)

14.5 将选项标准化

自己的选项采用同样的含义。

连接用户的方式(14处理用户输入)(29)

14.6 获得用户输入14.6.1 基本的读取

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

连接用户的方式(14处理用户输入)(30)

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

read命令包含了-p选项,允许你直接在read命令行指定提示符。只有一个变量的话,read命令会将提示符后输入的所有数据分配给单个变量。若指定多个变量,输入的每个数据值都会分配给变量列表中的下一个变量。如果变量数量不够,剩下的数据就全部分配给最后一个变量。

连接用户的方式(14处理用户输入)(31)

也可以在read命令行中不指定变量。如果是这样,read命令会将它收到的任何数据都放进特殊环境变量REPLY中。

连接用户的方式(14处理用户输入)(32)

14.6.2 超时

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

连接用户的方式(14处理用户输入)(33)

连接用户的方式(14处理用户输入)(34)

连接用户的方式(14处理用户输入)(35)

如果计时器过期,read命令会以非零退出状态码退出,可以使用如if-then语句或while循环这种标准的结构化语句来理清所发生的具体情况。在本例中,计时器过期时,if语句不成立,shell会执行else部分的命令。

也可以不对输入过程计时,而是让read命令来统计输入的字符数。当输入的字符达到预设的字符数时,就自动退出,将输入的数据赋给变量。

连接用户的方式(14处理用户输入)(36)

14.6.3 隐藏方式读取

-s选项可以避免在read命令中输入的数据出现在显示器上(实际上,数据会被显示,只是read命令会将文本颜色设成跟背景色一样)

连接用户的方式(14处理用户输入)(37)

14.6.4 从文件中读取

可以用read命令来读取Linux系统上文件里保存的数据。每次调用read命令,它都会从文件中读取一行文本。当文件中再没有内容时,read命令会退出并返回非零退出状态码。

其中最难的部分是将文件中的数据传给read命令。最常见的方法是对文件使用cat命令,将结果通过管道直接传给含有read命令的while命令。

连接用户的方式(14处理用户输入)(38)

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

,