如果一个脚本有多个命令行参数,调用这个脚本时所指定的命令行参数顺序不固定,那么即是参数的个数正确,脚本也不一定可以正常运行。如:
一个脚本(process.sh),可以接受三个参数:配置文件、输入数据的文件和脚本的数据文件(default.conf、input.txt、output.txt)。此脚本读取配置文件内容,处理input.txt文件,然后将输出写入到output.txt中。那么参数的顺序就由为重要了。因为:
bash process.sh default.conf input.txt output.txt
和
bash process.sh input.txt output.txt default.conf
完全是不同的。
为了避免上述的情况,和使得脚本更加严谨,当编写一个功能比较复杂的脚本时,通常要使得脚本具有可以指定选项的功能。如:对于上述的脚本文件process.sh,可以使用"-c"选项表示指定配置文件,"-o"选项指定输出文件。
使用case语句处理命令行选项
如果只接受一个命令行选项时,可以使用case语句进行处理。
如:
[root@rs1 case]# cat process.sh #将第一个命令行参数赋值给变量opt opt=$1 #将第二个命令行参数赋值给filename filename=$2 #定义函数checkfile checkfile() { #如果没有$2,则提示缺少文件,并退出 if test -z $filename then #显示缺少参数$2 echo "FIle name missing." #退出,返回状态码为1 exit 1 elif test ! -f $filename then #如果文件不存在,显示文件不存在 echo "The file $filename does not exist!" #退出,返回状态码2 exit 2 fi } case $opt in #匹配-e或者-E选项 -e|-E) #调用checkfile函数 checkfile echo "Editing $filename file..." #运行编辑文件的命令、函数或脚本 #Running command or function to edit the file. echo "I Like You But Just Like You" >> $filename ;; #匹配-p或者-P选项 -p|-P) #调用checkfile函数 checkfile echo "Displaying $filename file..." #运行显示文件的命令、函数或脚本 cat $filename ;; #匹配其他选项 *) echo "Bad arguement!" echo "Usage `basename $0` -e|-p filename" echo " -e filename: Edit file." echo " -p filename: Display file." ;; esac
执行脚本:
[root@rs1 case]# bash process.sh //不加参数,提示参数错误 Bad arguement! Usage process.sh -e|-p filename -e filename: Edit file. -p filename: Display file. [root@rs1 case]# bash process.sh -e //只添加选项,提示文件名丢失,即参数不完整 FIle name missing. [root@rs1 case]# bash process.sh -e 2.txt //添加参数完整,但是2.txt不存在 The file 2.txt does not exist! [root@rs1 case]# touch 2.txt //创建2.txt [root@rs1 case]# bash process.sh -e 2.txt //正确运行,edit file Editing 2.txt file... [root@rs1 case]# bash process.sh -p 2.txt //查看文件 Displaying 2.txt file... I Like You But Just Like You
使用getopt处理多个命令行选项
在使用命令时,我们可以进行选项合并:比如,使用命令ls -l -a -i时,可以写成ls -ali。这就要借助于getopts(或者getopt)来处理多命令行选项。
getopts是一个更专业的处理命令行选项和参数的工具,是Bash的内部命令,优势有三:
不需要额外的再通过一个外部程序处理位置参数
getopts可以更容易的解析Shell变量
getopts定义在POSIX中
getopt不能解析长选项
如:
只有一个选项
第一个脚本只有一个-a选项,没有任何参数
[root@rs1 getopts]# cat getopts.sh #使用getopts解析命令行选项,这里只有一个-a选性,选项字符串中的第一个字符为:(冒号),表示抑制错误报告 while getopts ":a" opt do case $opt in #匹配-a选项,注意这里case条件是a不是-a a) echo "Tht option -a was triggered." ;; \?) echo "Invalid option: -${OPTARG}" ;; esac done
当不指定任何选项时
[root@rs1 getopts]# bash getopts.sh [root@rs1 getopts]#
当没有指定选项,而是制定了一个命令行参数时
[root@rs1 getopts]# bash getopts.sh a [root@rs1 getopts]#
当指定了错误的选项时
[root@rs1 getopts]# bash getopts.sh -b Invalid option: -b
当指定了正确的选项时
[root@rs1 getopts]# bash getopts.sh -a Tht option -a was triggered.
当指定了多个选项,有对有错时
[root@rs1 getopts]# bash getopts.sh -a -b -c -f -ab Tht option -a was triggered. Invalid option: -b Invalid option: -c Invalid option: -f Tht option -a was triggered. Invalid option: -b
总结:
getopts没有看到任何有效的或者无效的选项,就不会被触发
如果运行时指定了一个命令行参数,而不是选项,同样不会被触发
当指定了一个错误的(没有被getopts解析)的选项时,getopts被出发了,但是没有接收这个选项,而是将\?放入变量opt中,并将无效的选项字符放入变量OPTARG中(这个变量被设置为由getopts找到的选项所对应的参数)
重复指定同一个选项多次,getopts就会被触发多次
所以需要注意:
无效的选项不会停止处理;如果希望脚本在接受到无效的选项时停止运行,就需要做一些完善工作。如在正确位置执行exit命令
多个相同的选项无意义出现时,应该禁止,所以还需要添加一些检查操作
多个选项和参数
[root@rs1 getopts]# cat getopts_2.sh #定义变量vflag vflag=off #定义变量filename filename="" #定义变量output output="" #定义函数usage usage() { echo "Usage:" echo " myscript [-h] [-v] [-f <filename>] [-o <filename>]" exit -1 } #在while循环中使用getopts解析命令行选项 #要解析的选项有-h、-v、-f和-o,其中-f和-o选项带有参数 #字符串选项中第一个冒号表示getopts使用抑制错误报告模式 while getopts :hvf:o: opt do case "$opt" in v) vflag=on echo "\$vflag : $vflag" ;; f) #将-f选项的参数赋值给变量filename filename=$OPTARG #如果文件不存在,则显示提示信息,并退出脚本 if test ! -f $filename then echo "The source file $filename does not exist!" exit 1 else echo "For -f `date` I Like You But Just Like You." >> $filename fi ;; o) #将-o选项的参数赋值给变量output output=$OPTARG #如果指定的输出文件不存在,就创建一个文件 if test ! -f $output then touch $output fi echo "For -o `date` I Like You But Just Like You." >> $output cat $output ;; h) #显示脚本的使用信息 usage exit 0 ;; :) #如果没有为需要参数的选项指定参数,则显示提示信息,并退出脚本运行 echo "The Option -$OPTARG requires an argument." exit 1 ;; ?) #如果指定的选项为无效选项,则显示提示信息,及脚本使用方法,并退出脚本运行 echo "Invalid option: -$OPTARG" usage exit 2 ;; esac done
当执行-h、-h选项时
[root@rs1 getopts]# bash getopts_2.sh -h Usage: myscript [-h] [-v] [-f <filename>] [-o <filename>] [root@rs1 getopts]# bash getopts_2.sh -v $vflag : on [root@rs1 getopts]# bash getopts_2.sh -vh $vflag : on Usage: myscript [-h] [-v] [-f <filename>] [-o <filename>]
单添加-f、-o选项时
对于-f选项
[root@rs1 getopts]# bash getopts_2.sh -f 1.txt The source file 1.txt does not exist! [root@rs1 getopts]# touch 1.txt [root@rs1 getopts]# bash getopts_2.sh -f 1.txt [root@rs1 getopts]# cat 1.txt For -f Mon Jul 9 02:53:04 CST 2018 I Like You But Just Like You.
对于-o选项
[root@rs1 getopts]# bash getopts_2.sh -o 4.txt For -o Mon Jul 9 02:53:34 CST 2018 I Like You But Just Like You.
当-f、-o同时使用时
[root@rs1 getopts]# bash getopts_2.sh -o 3.txt -f 3.txt For -o Mon Jul 9 02:53:53 CST 2018 I Like You But Just Like You. [root@rs1 getopts]# cat 3.txt For -o Mon Jul 9 02:53:53 CST 2018 I Like You But Just Like You. For -f Mon Jul 9 02:53:53 CST 2018 I Like You But Just Like You.
对于错误选项
[root@rs1 getopts]# bash getopts_2.sh -x Invalid option: -x Usage: myscript [-h] [-v] [-f <filename>] [-o <filename>]
使用getopt处理多命令行选项
getopt和getopts很像,也是用来解析命令行选项和参数,使其可以被Shell程序简单的解析。不同指出在于:getopt命令时Linux下的命令行工具,支持长选项。在脚本中的调用方式不同。
如:
第一个实例
[root@rs1 getopts]# cat getopt_1.sh #将getopt命令解析后的内容设置到位置参数 set -- `getopt f:vl "$@"` #如果位置参数的个数大于0,就执行while循环 while [ $# -gt 0 ] do #打印$1的值 echo $1 #将位置参数左移 shift done
执行脚本:
[root@rs1 getopts]# bash getopt_1.sh -vl -f1.txt param_1 -v -l -f 1.txt -- param_1
解释:getopt f:vl "$@"
"f:vl"中,vl被解析成"-v"和"-l",因为"-f"后面有冒号,所以-f1.txt被解析成"-f 1.txt"。然后解析后的命令行选项和参数之间使用双连字符(--)分割。
set命令中," getopt f:vl "$@" "表示将传递给脚本getopt_1.sh的命令行选项和参数作为getopt命令的参数,由getopt命令解析处理。整个set命令语句"set -- `getopt f:vl "$"`"表示将getopt命令的输出作为值一次赋值给位置参数。
第二个实例
[root@rs1 getopts]# cat getopt_2.sh #定义变量vflag vflag=off #定义变量filename filename="" #定义变量output output="" #使用getopt命令处理后的命令行选项和参数重新设置位置参数 set -- `getopt hvf:o: "$@"` #定义函数usage usage() { echo "Usage:" echo " myscript [-h] [-v] [-f <filename>] [-o <filename>]" exit -1 } #在while循环中使用getopts解析命令行选项 #要解析的选项有-h、-v、-f和-o,其中-f和-o选项带有参数 while [ $# -gt 0 ] do case "$1" in -v) vflag=on echo "\$vflag : $vflag" ;; -f) #将-f选项的参数赋值给变量filename filename=$2 #如果指定的作为参数的文件不存在,则显示文件不存在到标准输出,并退出脚本运行 #否则将位置左移,移到-f选项的参数位置 if test ! -f $filename then echo "The source file $filename does not exist!" exit 1 else shift echo "For -f `date` I Like You But Just Like You." >> $filename fi ;; -o) #将-o选项的参数赋值给便来嗯filename output=$2 #如果指定的输出文件不存在,则显示文件不存在到标准输出,并退出脚本运行 #否则将位置参数左移,移动到-o选项的参数的位置 if test ! -d `dirname $output` then echo "The output path `dirname $output` does not exist!" exit else shift echo "For -o `date` I Like You But Just Like You." >> $output cat $output fi ;; --) #如果是--,则跳过,并退出while循环 shift break ;; -*) echo "Invalid option: $1" usage exit 2 ;; *) break ;; esac shift done
执行脚本:
[root@rs1 getopts]# bash getopt_2.sh -h //使用-h选项 Usage: myscript [-h] [-v] [-f <filename>] [-o <filename>] [root@rs1 getopts]# bash getopt_2.sh -vf //使用-vf,被解析成-v -f,-v不需要参数,可以执行。-f需要参数,所以提示getopt: option requires an argument -- 'f' getopt: option requires an argument -- 'f' $vflag : on [root@rs1 getopts]# bash getopt_2.sh -f 1.txt //选项和参数都正确,执行成功 [root@rs1 getopts]# cat 1.txt For -f Mon Jul 9 02:53:04 CST 2018 I Like You But Just Like You. For -f Mon Jul 9 03:53:08 CST 2018 I Like You But Just Like You. //日期是和电脑时间同步,刚执行结束的 [root@rs1 getopts]# date Mon Jul 9 03:53:16 CST 2018 [root@rs1 getopts]# bash getopt_2.sh -f 1.txt -o //-o也需要参数,所以提示缺少参数 getopt: option requires an argument -- 'o' [root@rs1 getopts]# bash getopt_2.sh -f 1.txt -o /mnt/ getopt_2.sh: line 55: /mnt/: Is a directory cat: /mnt/: Is a directory [root@rs1 getopts]# bash getopt_2.sh -f 1.txt -o 2.txt //执行成功,-o参数为2.txt Mon Jul 9 02:48:46 CST 2018 I Like You But Just Like You. For -o Mon Jul 9 03:35:58 CST 2018 I Like You But Just Like You. For -o Mon Jul 9 03:37:56 CST 2018 I Like You But Just Like You. For -o Mon Jul 9 03:38:03 CST 2018 I Like You But Just Like You. For -o Mon Jul 9 03:53:48 CST 2018 I Like You But Just Like You. //最新的结果,因为电脑此时时间为下: [root@rs1 getopts]# date Mon Jul 9 03:53:53 CST 2018
执行一次全选项,分析过程:
[root@rs1 getopts]# bash -x getopt_2.sh -vf /etc/passwd -o 2.txt + vflag=off + filename= + output= ++ getopt hvf:o: -vf /etc/passwd -o 2.txt + set -- -v -f /etc/passwd -o 2.txt -- //六个变量 -v -f /etc/passwd -o 2.txt --。这是getopt进行解析的过程,十分重要 + '[' 6 -gt 0 ']' + case "$1" in //此时,-v为$1 + vflag=on + echo '$vflag : on' $vflag : on + shift + '[' 5 -gt 0 ']' + case "$1" in //此时,-f为$1,$2为"/etc/passwd"字符串 + filename=/etc/passwd //这一步将-f选项的参数$2赋值给变量filename + test '!' -f /etc/passwd //不为空 + shift //-f选项被丢掉,$1变成"/etc/passwd" ++ date + echo 'For -f Mon Jul 9 03:38:03 CST 2018 I Like You But Just Like You.' + shift //执行了最后的shift,丢掉了"/etc/passwd",此是$1时-o + '[' 3 -gt 0 ']' //此时,$1是-o + case "$1" in + output=2.txt //将-o的参数,2.txt赋值给output变量 ++ dirname 2.txt //执行命令 + test '!' -d . //测试2.txt时不是目录文件,经过测试不是 + shift //此时$1变成2.txt ++ date + echo 'For -o Mon Jul 9 03:38:03 CST 2018 I Like You But Just Like You.' + cat 2.txt Mon Jul 9 02:48:46 CST 2018 I Like You But Just Like You. For -o Mon Jul 9 03:35:58 CST 2018 I Like You But Just Like You. For -o Mon Jul 9 03:37:56 CST 2018 I Like You But Just Like You. For -o Mon Jul 9 03:38:03 CST 2018 I Like You But Just Like You. + shift //此是$1变成-- + '[' 1 -gt 0 ']' //因为$1仍然有值 + case "$1" in //,$1的值是--,所以准备跳出while循环 + shift //执行esac后面的shirt + break //跳出脚本运行
getopt支持长选项
[root@rs1 getopts]# cat getopt_3.sh #定义变量ARG_B ARG_B=0 #使用getopt命令来处理脚本的命令行选项和参数,再将处理后的结果重新赋值给位置参数 #eval命令用于将其后的内容作为单个命令读取和执行,这里用于处理getopt命令生成的参数的转义字符 eval set -- `getopt -o a::bc: --long arga::,argb,argc: -n 'getopt_3.sh' -- "$@"` #执行while循环 while true do case "$1" in -a|--arga) case "$2" in "") #当没有给出参数时,使用默认赋值变量ARG_A ARG_A='default value' shift ;; *) #当给具有可选参数的选项-a(--arga)指定了参数时,使用指定的参数赋值给变量ARG_A ARG_A=$2 shift ;; esac ;; -b|--argb) #如果指定了-b选项,则将变量ARG_B赋值为1 ARG_B=1 ;; -c|--argc) case "$2" in "") #如果为-c选项没有指定参数,则跳过 shift ;; *) #如果为-c选项指定了参数,则将指定的参数赋值给变量ARG_C ARG_C=$2 shift ;; esac ;; --) #若为双连接字符则退出while循环,表示选项结束 shift break ;; *) #若为其他选项,则显示错误信息,并退出脚本的执行 echo "Internal error!" exit 1 ;; esac shift done #打印变量ARG_A、ARG_B、ARG_C的值 echo "ARG_A = $ARG_A" echo "ARG_B = $ARG_B" echo "ARG_C = $ARG_C"
从脚本中可以看出,我们可以指定短选项进行运行:
[root@rs1 getopts]# bash getopt_3.sh -s -afsx
getopt_3.sh: invalid option -- 's'
ARG_A = fsx
ARG_B = 0
ARG_C =
[root@rs1 getopts]# bash getopt_3.sh -afsx -b -c 123
ARG_A = fsx
ARG_B = 1
ARG_C = 123
[root@rs1 getopts]# bash getopt_3.sh -c 123
ARG_A =
ARG_B = 0
ARG_C = 123
[root@rs1 getopts]# bash getopt_3.sh -c //如果使用-c,就必须添加一个参数
getopt_3.sh: option requires an argument -- 'c'
ARG_A =
ARG_B = 0
ARG_C =
看看getopt的使用:
getopt -o a::bc: --long arga::,argb,argc: -n 'getopt_3.sh' -- "$@"
其中:
"-o"选项表示让getopt时别哪些短选项。如:-a、-b、-c
"--long"选项表示让getopt时别哪些长选项。如:--arga、--argb、--argc
"-n"选项告诉getopt程序报告错误时使用什么文件名
"-o"选项所指定的选项字符串遵循如下原则:
每个字符代表一个选项
字符后跟一个冒号表示需要一个参数
字符后跟两个冒号表示可有个可选参数
总结
对于到底使用getopts还是使用getopt,需要根据需求来决定。如果希望Shell脚本支持长选项,则使用getopt,如果考虑到跨平台的兼容性,或者不需要长选项,推荐使用getopts。