7.处理选项
选项是跟在单折线后面的单个字母,它能改变命令的行为。下面将介绍3种在脚本中处理选项的方法。
7.1 查找选项
处理简单选项:在提取每个单独参数时,用case语句来判断某个参数是否为选项。如下例所示:
#!/bin/bash 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 # 结果 [njust@njust tutorials]$ ./bar16.sh -a -b -c -d Found the -a option Found the -b option Found the -c option -d is not an option
分离参数和选项:在shell脚本中同时使用选项和参数的情况,Linux中处理这个问题的标准方法是用特殊字符来将两者分开,该字符会告诉脚本何时选项结束以及普通参数何时开始。对Linux来说,这个特殊字符是--,shell会用双破折线来表明选项列表已经结束。在双破折线后,脚本就可以放心将剩下的命令行参数当作参数,而不是选项。如下例所示:
#!/bin/bash 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 # 结果 [njust@njust tutorials]$ ./bar17.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
处理带值的选项:有些选项会带上一个额外的参数值,如./bar666.sh -a test1 -b -c -d test2。当命令行选项要求额外的参数时,脚本必须能检测到并正确处理。如下例所示:
#!/bin/bash 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 # 结果 [njust@njust tutorials]$ ./bar19.sh -a -b test1 -f -- demo1 demo2 Found the -a option Found the -b option,with parameter value test1. -f is not an option Parameter #1: demo1 Parameter #2: demo2
7.2 使用getopt命令
getopt命令是一个在处理命令行选项和参数时非常方便的工具,它能识别命令行参数,从而在脚本中解析它们时更方便。getopt命令可以接受一系列任意形式的命令行选项和参数,并自动转换成适当的格式,具体命令格式如下所示:
getopt optstring parameters
optstring定义了命令行有效的选项字母,还定义了哪些选项字母需要参数值。optstring中列出了要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后加一个冒号。注意:getopt命令有一个更高级的版本即getopts,注意两者的区别。具体实例如下所示:
[njust@njust tutorials]$ getopt ab:cd -a -b test1 -cd test2 test3 -a -b test1 -c -d -- test2 test3
上述示例中,optstring定义了四个有效选项字母:a、b、c、d。字母b后面的冒号表示b选项需要一个参数值。注意:getopt命令会将-cd选项分成两个单独的选项,并插入双破折线来分隔行中的额外参数。如果指定了一个不在abcd选项中的参数,默认情况下,getopt命令会产生一条错误的信息。如下所示:
[njust@njust tutorials]$ getopt ab:cd -a -b test1 -cdf test2 test3 getopt:无效选项 -- f -a -b test1 -c -d -- test2 test3
在脚本文件中使用getopt:用getopt命令生成的格式后的版本来替换自己已有的命令行选项和参数,用set命令能够做到。set命令使用的方法如下:
set -- $(getopt -q ab:cd "$@")
利用上述方法,可以帮我们处理命令行参数的脚本。如下例所示:
#!/bin/bash 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 # 结果 [njust@njust tutorials]$ ./bar20.sh -ac # 合并选项情况下也能处理! Found the -a option Found the -c option
但是,getopt命令也存在一个小问题。它并不擅长处理带空格和引号的参数值。它会将空格当成参数分隔符,而不是根据双引号将两者看成一个参数,解决方法是使用更高级的getopts命令。
[njust@njust tutorials]$ ./bar20.sh -a -b test1 -c "test2 test3" test4 Found the -a option Found the -b option,with parameter value 'test1'. Found the -c option Parameter #1: 'test2 Parameter #2: test3' Parameter #3: 'test4'
7.4 更高级的getopts命令
getopts命令是shell中的内建命令,它比getopt命令多了一些扩展功能。getopt命令将命令行上选项和参数处理后只生成一个输出,而getopts命令能够和已有的shell参数变量配合使用。每次调用它时,它一次只处理命令行上检测到的一个参数,处理完所有的参数后,它会退出并返回一个大于0的退出状态码,这让它非常适合用解析命令行所有参数的循环中。getopts命令的格式如下:
getopts optstring variable
optstring值类似于getopt命令中的那个,getopts命令将当前参数保存在命令行中定义的variable中。getopts命令会用到两个环境变量,如果选项需要跟一个参数值,OPTARG环境变量就会保存这个值,OPTIND环境变量保存了参数列表中getopts正在处理的参数位置,这样你就能在处理完选项后继续处理其他命令行参数了。如下例所示:
#!/bin/bash while getopts :ab:c opt do case "$opt" in a) echo "Found the -a option";; # 注意:getopts命令在解析命令行选项时会移除开头的单破折线,所以在case定义中不用单破折线! b) echo "Found the -b option,with value $OPTARG";; c) echo "Found the -c option";; *) echo "Unknown option: $opt";; esac done # 结果 [njust@njust tutorials]$ ./bar21.sh -ab test1 -c Found the -a option Found the -b option,with value test1 Found the -c option
getopts命令的优点1:可以在参数值中包含空格,如下例所示:
[njust@njust tutorials]$ ./bar21.sh -b "test1 test2" -a Found the -b option,with value test1 test2 Found the -a option
getopts命令的优点2:是将选项字母和参数值放在一起使用,而不用加空格。如下例所示:
[njust@njust tutorials]$ ./bar21.sh -abdemo1 Found the -a option Found the -b option,with value demo1
此外,getopts命令还可以将命令行上找到的所有未定义的选项统一输出为问号,如下例所示:
[njust@njust tutorials]$ ./bar21.sh -acdf Found the -a option Found the -c option Unknown option: ? Unknown option: ?
getopts命令知道何时停止处理选项,并将参数留给你处理。在getopts处理每个选项时,它会将OPTIND环境变量值加1,在getopts完成处理时,使用shift命令和OPTIND值来移动参数。如下例所示:
#!/bin/bash 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 ] count=1 for param in "$@" do echo "Parameter $count:$param" count=$[ $count + 1 ] done # 结果 [njust@njust tutorials]$ ./bar22.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
8.选项标准化
下面是Linux中常用到的一些命令行选项的含义:
选项 含义 -a 显示所有对象 -c 生成一个计数 -d 指定一个目录 -e 扩展一个对象 -f 指定读入数据的文件 -h 显示命令的帮助信息 -i 忽略文本大小写 -l 产生输出的长格式版本 -n 使用非交互模式 -o 将所有输出重定向到指定的输出文件 -q 以安静模式运行 -r 递归地处理目录和文件 -s 以安静模式运行 -v 生成详细输出 -x 排除某个对象 -y 对所有问题回答yes
9.获取用户输入
尽管命令行选项和参数是从脚本用户处获得输入的一种重要方式,但是有时候脚本的交互性还需要更强一些。例如在运行脚本时问一个问题,并等待运行脚本的人来回答,bash shell为此提供了read命令。
9.1 基本的读取
read命令从标准输入或另一个文件描述符中接收输入。在收到输入后,read命令会将数据放进一个变量,如下例所示:
#!/bin/bash echo -n "Please Enter your name: " # -n选项不会在字符串末尾输出换行符,允许脚本用户紧跟其后输入数据,而不是在下一行输入! read name echo "Hello $name,welcome to my home." # 结果 [njust@njust tutorials]$ ./bar23.sh Please Enter your name: Curry # 自己输入的! Hello Curry,welcome to my home.
read命令包含了-p选项,它允许你直接在read命令行指定提示符,如下例所示:
#!/bin/bash read -p "Please Enter your age: " age days=$[ $age * 365 ] echo "That makes you over $days days old" # 结果 [njust@njust tutorials]$ ./bar24.sh Please Enter your age: 28 That makes you over 10220 days old
也可以在read命令行中不指定变量,read命令会将它收到的任何数据都放进特殊环境变量REPLY中,REPLY环境变量会保存输入的所有数据,可以在shell脚本中像其他变量一样使用。如下例所示:
#!/bin/bash read -p "Enter your name: " echo "Hello $REPLY,welcome to NewYork." # 结果 [njust@njust tutorials]$ ./bar25.sh Enter your name: Stephen Curry Hello Stephen Curry,welcome to NewYork.
9.2 超时
使用read命令时一定要注意,脚本很可能会一直等待用户的输入。如果不管是否有数据输入,脚本都必须继续执行,可以使用-t选项来指定一个计数器。-t选项指定了read命令等待输入的秒数,当计数器超时后,read命令会返回一个非零退出状态码,如下例所示:
#!/bin/bash if read -t 5 -p "Please enter your name: " name then echo "Hello $name,welcome to NewYork." else echo echo "Sorry, it's wrong!" fi # 结果 [njust@njust tutorials]$ ./bar26.sh Please enter your name: Sorry, it's wrong!
也可以不对输入过程计时,而是让read命令来统计输入的字符数,当输入的字符达到预设的字符数时,就自动退出,将输入的数据赋值给变量。如下例所示:
#!/bin/bash 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" # 结果 [njust@njust tutorials]$ ./bar27.sh Do you want to continue [Y/N]? Y Fine,continue on... This is the end of the script
上述示例代码段中,-n选项与值1一起使用,它告诉read命令在接收单个字符后退出。只要按下单个字符后,read命令就会接收输入并将它传给变量,无需按下回车键。
9.3 隐藏方式读取
有时候需要从用户输入处得到输入,但又不能在屏幕中显示输入。其中典型的例子就是输入的密码,此外还有很多其他需要隐藏的数据类型。-s选项可以避免在read命令中输入的数据出现在屏幕上,如下例所示:
#!/bin/bash read -s -p "Enter you password: " passwd echo echo "Is your password really $passwd? " # 结果 [njust@njust tutorials]$ ./bar28.sh Enter you password: Is your password really 12345?
9.4 从文件中读取
可以用read命令来读取Linux系统上文件中保存的数据,每次调用read命令,它都会从文件中读取一行文本。当文本中没有内容时,read命令会退出并返回非零退出状态码。注意:最难的部分是将文件中的数据传给read命令,最常见的方法是对文件使用cat命令,将结果通过管道直接传给含有read命令的while命令。如下例所示:
#!/bin/bash count=1 cat demodemo | while read line do echo "Line $count: $line" count=$[ $count + 1 ] done echo "Finished processing the file" # 结果 [njust@njust tutorials]$ ./bar29.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 Romeo? Finished processing the file