设为首页 收藏本站
查看: 928|回复: 0

Shell脚本中参数处理方法

[复制链接]
累计签到:8 天
连续签到:1 天
发表于 2015-10-26 11:53:40 | 显示全部楼层 |阅读模式
在Shell脚本中处理命令行参数,可以使用getopts/getopt来进行——当然,手工解析也是可以的。
下面通过一个特定的情景来讲一下这三种参数处理方法。
这两天写了一个安全删除的脚本,原理就是将指定的文件移动到某个特定的目录下并保存其原始路径信息,这和在Windows下以及在Linux的桌面环境下"将文件移动到回收站"的意义是一样的。就拿这个来做例子吧。


在这个脚本中,有五个选项,分别代表五种动作:




  • -d : 将文件移动到回收站,该选项后需要指定一个文件或目录名

  • -l : 列出被移动到回收站的文件及其id,该选项不需要值

  • -b : 恢复被移动到回收站的文件,该选项需要指定一个文件对应的id

  • -c : 清空回收站,该选项不需要值

  • -h : 打印帮助信息


手工解析



所谓的手工解析,就是取到参数后手工一个一个解析了,以下是手工解析上述情景参数的过程:


while [ $# -gt 0 ];do
case $1 in
-d)
shift
file_to_trash=$1
trash $file_to_trash # trash is a function
;;
-l)
print_trashed_file  # print_trashed_file is a function
;;
-b)
shift
file_to_untrash=$1
untrash $file_to_untrash # untrash is a function
;;
-c)
clean_all           # clean all is a function
;;
-h)
usage
exit 0
;;
\?)
usage
exit 1
;;
esacdone

这里用到了'shift'这个命令,这个命令的作用是将参数列表以空格为分隔符左移一个单位,或者可以理解为将第一个参数给去掉了,比如获取的命令行参数为:


-d hello.txt


在执行了'shift'后,命令行参数就变成了


hello.txt


这样,在使用了shift后,我们每次都只要去看参数列表中的第一个就行了。当然,其实不用'shift'也是可以的,比如说这样:


i=1
while [ $i -le $# ];do
case ${!i} in
-d)
i=$(expr $i + 1)
file_to_trash=${!i}
trash $file_to_trash # trash is a function
;;
-l)
print_trashed_file  # print_trashed_file is a function
;;
-b)
i=$(expr $i + 1)
file_to_untrash=${!i}
untrash $file_to_untrash # untrash is a function
;;
-c)
clean_all           # clean all is a function
;;
-h)
usage
exit 0
;;
\?)
usage
exit 1
;;
esac
i=$(expr $i + 1)
done

对比可以发现使用'shift'会稍微方便一点。


当然,上面的处理没有进行参数检查,这些检查应该要防止这些错误情况:参数个数为0、完全冲突的"动作"一起出现、选项需要值但未给值。



getopts



'getopts'是POSIX Shell中内置的一个命令,其使用方法是:


getopts <opt_string> <optvar> <arguments>


下面是在Shell中使用该命令的一个示例:



DSC0000.jpg




本质上来说,'getopts'的处理和我们手工处理是差不多的,它不过是提供了更便利的方式而已。它的使用方式非常简单明了,其形式为:


while getopts <opt_string> <optvar>
case $<optvar> in
# ...
esacdone

其中<opt_string>是要处理的选项的一个集合,每个选项在其中用不包含连字符'-'的字母来表示,每个代表选项的字母前后可以有一个冒号,前面有冒号表示当处理该选项出错时不输出'getopts'自身产生的错误信息,这方便我们自己编写对应的错误处理方法;后面的冒号表示这个选项需要一个&#20540;。对于我们这个&quot;安全删除&quot;的例子,这个<opt_string>应该是:


d:lb:ch


冒号的归属的话,先到先得吧,大概是这样。


在使用'getopts'时,有两个特殊的变量,它们是 OPTINDOPTARG ,前者表示当前参数在参数列表中的位置——相当于手工解析第二种方法中那个自定义的变量 i ,其&#20540;初始时为1, 会在每次取了选项以及其&#20540;(如果有的话)后更新; OPTARG 则是在选项需要&#20540;时,存储这个选项对应的&#20540;。这样,我们这个例子用'getopts'就可以写成:


while getopts d:lb:ch OPT;do
case $OPT in
d)
file_to_trash=$OPTARG
trash $file_to_trash # trash is a function
;;
l)
print_trashed_file  # print_trashed_file is a function
;;
b)
file_to_untrash=$OPTARG
untrash $file_to_untrash # untrash is a function
;;
c)
clean_all           # clean all is a function
;;
h)
usage
exit 0
;;
\?)
usage
exit 1
;;
esacdone

对比可以看到,相比手工解析的第一种办法,又更为简洁一点了。不过需要注意的是,'getopts'会从第一个参数开始,只按照<opt_string>指定的形式来寻找并解析参数,如果给出的实际命令行参数与其所描述的参数形式不符,则会出错中止。


比如说,对于上面的例子,假设这个脚本已经完全写好了,脚本名为 trash.sh ,其参数处理就是上面这样,那么如果我在终端里执行:


./trash.sh a -b hello.txt


开始那个多余的参数'a'将会导致'getopts'在解析到选项'-b'前就出错终止。所以呢,像使用'getopts'这样的方法,其自由度不如手工解析,如果要保证脚本在任何情况下都能正确解析参数,它需要多做一点——当然啦,上面这个愚蠢的错误使用情况还是比较少出现的啦,反正我现在写的脚本里压根没考虑这样的情况。



getopt



'getopt'与'getopts'类&#20284;,不过'getopts'只能处理短选项,'getopt'则能处理短选项和长选项。所谓的短选项就是类&#20284;下面这样的选项:


-a


而下面这样的则是长选项


--action=delete


当然,事无绝对,通过一些技巧,用'getopts'处理长选项也是可能的。这里先说一下如何用'getopt'来处理参数吧。


需要事先说明的一点是,'getopt'不是Shell内建的命令,而是'util-linux'这个软件包提供的功能,它不是POSIX标准的一部分,所以也有人建议不使用'getopt'。


首先将之前说到的五种动作对应的短选项扩展一下,以便讲解'getopt'的使用:




  • -d/–delete : 将文件移动到回收站,该选项后需要指定一个文件或目录名

  • -l/–list : 列出被移动到回收站的文件及其id,该选项不需要&#20540;

  • -b/–back : 恢复被移动到回收站的文件,该选项需要指定一个文件对应的id

  • -c/–clear : 清空回收站,该选项不需要&#20540;

  • -h/–help : 打印帮助信息


'getopt'既能处理短选项也能处理长选项,短选项通过参数 -o 指定,长选项通过参数 -l 指定。同'getopts'一样,它一次也只解析一个选项,所以也需要循环处理,不过与'getopts'不同的是,'getopt'没有使用 OPTINDOPTARG 这两个变量,所以我们还得手动对参数进行'shift',对需要&#20540;的选项,也得手动去取出&#20540;。


下面是在Shell中使用'getopt'的一个示例:






可以看到,'getopt'将参数中以下形式的内容:


--longopt=argument


在返回结果中替换成下面这样的形式:


--longopt argument


这样就可以通过循环和'shift'来进行处理了,不过在脚本中,'shift'命令是对命令行参数起作用的,即特殊变量&quot;$@&quot;,而我们在脚本中只能将'getopt'的返回结果作为字符串存储到一个变量中。为了让'shift'起作用,通常还要使用'set'命令来将变量的&#20540;赋给&quot;$@&quot;这个特殊变量。


真是有够麻烦的……算了,下面再集中吐槽吧……


然后,在设置好短选项和长选项后,在将实际的参数传给'getopt'时,要在实际参数前加上一个两个连字符 -- ,而'getopt'会将这两个连字符放到返回结果的最后面,在处理时可以将这两个连字符视为结束标志。


以下是针对本文假设的情景,使用'getopt'解析参数的流程:


arg=$(getopt -o d:lb:ch -l delete:,list,back:,clear,help -- $@)
set -- &quot;$arg&quot;
while true
do
case $1 in
-d|--delete)
file_to_trash=$2
trash $file_to_trash # trash is a function
shift 2
;;
-l|--list)
print_trashed_file  # print_trashed_file is a function
shift
;;
-b|--back)
file_to_untrash=$2
untrash $file_to_untrash # untrash is a function
shift
;;
-c|--clear)
clean_all           # clean all is a function
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
break
;;
esacdone

然而,知道了'getopt'的使用及其原理后,自然而然地可以发现,我可以不用去管这个结束标志,用&quot;$#&quot;这个表示参数个数的特殊变量,同样可以控制参数解析的流程,这完全和手工解析是同一个道理。我甚至可以将'getopt'的返回结果存储到一个数组里,直接循环处理这个数组,而不用使用'set'命令了。


好了,吐槽时间。


我之前写脚本都是用的'getopts',一来我用不上长选项,二来'getopts'的使用足够简单。在写本文之前,我倒是知道'getopt'可以处理长选项,但没仔细了解过。这两天了解了一下,觉得还是别用'getopt'的好,理由如下:




  • 'getopt'不是Shell内建命令,跨平台使用时可能会出现问题;

  • 只是将'–longopt=val'这样的参数形式替换成了'–longopt val',但因此增加了许多复杂性,比如使用了'set'命令,在使用'set'命令时还要考虑'getopt'的返回结果中有无Shell命令,有的话应该使用'eval'命令来消除可能导致的错误
    eval set -- &quot;$arg&quot;

  • 调用完还要进行与手工解析类&#20284;的工作,相比手工解析,并没有多大优势;

  • 真的需要长选项吗?我觉得短选项就足够了


getopts处理长选项



既然不建议使用'getopt',那么怎么处理长选项呢?自然是有办法的。


为了方便讲解,这里假设一个简单的情景吧,在这个情景里,我们只需要处理两个可能的选项




  • -f/–file: 设置文件名,该选项需要&#20540;

  • -h/–help: 打印帮助信息,该选项不需要&#20540;


用'getopts'处理这种情况,可以这么做:


filename=&quot;&quot;while getopts f:h-: opt;do
case $opt in
-)
case $OPTARG in
help)
usage
exit 0
;;
file=*)
filename=${OPTARG#*=}
;;
esac
f)
filename=$OPTARG
;;
h)
usage
exit 0
;;
\?)
usage
exit 1
;;
esacdone

当然,也许并不比手工解析简洁多少,但用起来肯定是比'getopt'要舒服的。



在函数中解析参数



有时候,我们也许想把参数解析的工作放到函数中去做,比如说定义了一个'main'函数然后在'main'函数中封装整个流程处理逻辑。又或者像我一样,写了几个小小的工具函数,放到了Bash的配置文件 .bashrc 中,参数解析的工作必须得在函数中做。


手工解析是能想到的最直接的办法,简单可行。


不过假如我们想用'getopts'来处理呢?动手尝试后,你会发现直接在函数中使用'getopts'是会出错的。要在函数中使用'getopts',必须在这个函数中使用'getopts'前,将 OPTIND 这个被'getopts'使用的特殊变量设置为函数局部变量,像这样:


function main() {
local OPTIND
while getopts d:lb:ch OPT;do
case $OPT in
d)
file_to_trash=$OPTARG
trash $file_to_trash # trash is a function
;;
l)
print_trashed_file  # print_trashed_file is a function
;;
b)
file_to_untrash=$OPTARG
untrash $file_to_untrash # untrash is a function
;;
c)
clean_all           # clean all is a function
;;
h)
usage
exit 0
;;
\?)
usage
exit 1
;;
esac
done
}
main $@

就是这样啦!






  • Previous

  • Archive

  • Next




最新 最早 最热


  • 评论



  • 还没有评论,沙发等你来抢


社交帐号登录:




  • 微博

  • QQ

  • 人人

  • 豆瓣

  • 更多»





发布


Linusp's
Blog正在使用多说

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-130933-1-1.html 上篇帖子: linux中查看现在使用的shell是ksh还是bash?以及怎样修改? 下篇帖子: Linux的Shell Script学习
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表