shell 命令行参数(getopt和getopts)
这里还有上一篇,这部分是基础:
https://blog.51cto.com/steed/2443313 getopt 命令使用getopt命令,可以解析任何命令行选项和参数,但是用法比较复杂。getopt的命令用法如下: $ getopt --help 用法: getopt optstring parameters getopt [options] [--] optstring parameters getopt [options] -o|--options optstring [options] [--] parameters 选项: -a,--alternative 允许长选项以 - 开始 -h,--help 这个简短的用法指南 -l,--longoptions <长选项> 要识别的长选项 -n,--name <程序名> 将错误报告给的程序名 -o,--options <选项字符串> 要识别的短选项 -q,--quiet 禁止 getopt(3) 的错误报告 -Q,--quiet-output 无正常输出 -s,--shell <shell> 设置 shell 引用规则 -T,--test 测试 getopt(1) 版本 -u,--unquoted 不引用输出 -V,--version 输出版本信息 $ 用法一共有3种格式,下面都会用到。 在命令行中简单使用先看第一个最简单的格式: getopt optstring parameters 第一部分是命令名。 $ getopt ab:cd -ad value1 -b best1 value2 value3 -a -d -b best1 -- value1 value2 value3 $ 主要理解 $ getopt ab:cd -ad value1 -b getopt:选项需要一个参数 -- b -a -d -- value1 $ 使用双破折线 $ getopt ab:cd -- -ad value1 -b best1 value2 value3 -- -ad value1 -b best1 value2 value3 $ getopt ab:cd -ad value1 -- -b best1 value2 value3 -a -d -- value1 -b best1 value2 value3 $ 这依然是是命令用法的第一种格式,双破折线是parameters内容的一部分。 参数包含空格的问题 支持长选项参考上面的示例,加上长选项的支持。使用长选项的示例如下: $ getopt -o ab:cd --long arga,argb:,argc,argd -- -ad -b best --argd value1 value2 -a -d -b ‘best‘ --argd -- ‘value1‘ ‘value2‘ $ 这是命令用法的第三种格式。 错误报告引用的程序名之前已经试过一次解析错误的报告了: $ getopt ab:cd -ad value1 -b getopt:选项需要一个参数 -- b -a -d -- value1 $ 这里错误报告的是getopt错误,可以把这个默认的内容替换掉。一般是换成执行的脚本的名字。 $ getopt -- ab:cd -ad value1 -b best1 -a -d -b ‘best1‘ -- ‘value1‘ $ 这样在双破折线前面就可加getopt命令的选项,这里要指定-n选项: $ getopt -n test.sh -- ab:cd -ad value1 -b test.sh:选项需要一个参数 -- b -a -d -- ‘value1‘ $ 这里看到包裹错误是,名字已经被替换掉了。 禁止错误报告 $ getopt -n test.sh -q -- ab:cd -ad value1 -b -a -d -- ‘value1‘ $ 可选参数还有一种可选参数,使用两个冒号。这个选项可以有一个或零个参数: $ getopt -o a::bc: -l arga::,argb,argc: -- -a value1 --arga value2 -a ‘‘ --arga ‘‘ -- ‘value1‘ ‘value2‘ $ getopt -o a::bc: -l arga::,argc: -- -avalue1 --arga=value2 -a ‘value1‘ --arga ‘value2‘ -- $ 第一次执行是传递的参数是错误的。因为是可选参数,参数和值之间不能有空格隔开,否则会有歧义。必须要连在一起才能认为是前一个选项的参数。否则就被认作是独立的参数了。 小结getopt 命令的选项所指定的选项字符串的规则:
在脚本中使用 getopt现在已经可以用getopt命令,将命令行参数按照规定的格式解析成规整的格式了。并且在解析过程中,还能发现参数格式错误的情况并报告。 set 命令要在脚本中使用getopt。首先,要用getopt命令生成格式化后的版本来替换已有的命令行选项和参数。需要用到set命令。 set -- $(getopt ab:cd "[email?protected]") 现在原始的命令行参数变量的值会被getopt命令的输出替换。而getopt已经为我们格式化好了命令行参数。 直接使用在之前编写的脚本的基础上,只要在开头加上一行代码,就可以直接使用了: set -- $(getopt a:b:s:u "[email?protected]") 加上这句后,就是让后续的代码处理getopt返回的参数,而不是调用命令时的命令行参数。 $ ./format.sh -u -a after -b befor value1 value2 value3 BEFOR_VALUE1_AFTER BEFOR_VALUE2_AFTER BEFOR_VALUE3_AFTER $ ./format.sh -u -a after -b befor value1 "value2 value3" value4 BEFOR_VALUE1_AFTER BEFOR_VALUE2_AFTER BEFOR_VALUE3_AFTER BEFOR_VALUE4_AFTER $ 第二条命令并不能处理带空格的参数,因为这里使用的是getopt的第一种格式。 使用第二种格式来解析要处理空格,就需要使用第二种格式(或者第三种),将命令修改为如下: set -- $(getopt -- a:b:s:u "[email?protected]") 简单的在最前面加上双破折线就好了。这条语句是错误的,后面还要修改。 再来验证一下: $ ./format.sh -u -a after -b befor value1 "value2 value3" value4 ‘BEFOR‘_‘VALUE1‘_‘AFTER‘ ‘BEFOR‘_‘VALUE2_‘AFTER‘ ‘BEFOR‘_VALUE3‘_‘AFTER‘ ‘BEFOR‘_‘VALUE4‘_‘AFTER‘ $ 使用第二、第三种格式,会用引号来限定参数的内容。但是引号干扰了set命令。 使用 eval 命令
关于eval命令,还有一种使用的情景。有时候在脚本中拼接出来的字符串即使打印出来看正确。并且直接复制、粘贴在交互界面中也能正确读被当做命令运行。但是却无法在脚本中被执行。这个时候就可以使用eval命令来解决。它能够把字符串当做命令来执行。 修改命令如下: eval set -- $(getopt -- a:b:s:u "[email?protected]") 再次验证: $ ./format.sh -u -a after -b befor value1 "value2 value3" value4 BEFOR_VALUE1_AFTER BEFOR_VALUE2 VALUE3_AFTER BEFOR_VALUE4_AFTER $ 第一种格式加上eval命令也是没有问题的,所以可以无脑用上。 解决空格问题只要能正确的使用getopt的第二种或第三种格式,那么参数包含空格的问题也就解决了。看上一小节。 参数解析错误并退出执行命令时,使用错误的参数,当前的效果如下: $ ./format.sh -u -w -a after -b befor value1 "value2 value3" value4 getopt:无效选项 -- w BEFOR_VALUE1_AFTER BEFOR_VALUE2 VALUE3_AFTER BEFOR_VALUE4_AFTER $ 解析发现问题了,并且报告了,但是脚本没有终止,而是继续执行。如果要判断出解析错误,就需要使用$?参数。然后退出脚本则是用exit命令。 getopt_cmd=$(getopt -n $(basename $0) -- a:b:s:u "[email?protected]") [ $? -ne 0 ] && exit 1 eval set -- "$getopt_cmd" 这里还加上了报告错误时名称的定义。exit退出时也要指定退出状态为非0,因为是运行错误。 验证效果: $ ./format.sh -v -a after -w -b format.sh:无效选项 -- v format.sh:无效选项 -- w format.sh:选项需要一个参数 -- b $ echo $? 1 $ 现在解析有问题后,就会直接退出。 完整的代码示例这里加上长选项以及可选参数的功能。
参数比较多,加了 完整代码如下: $ cat format.sh #!/bin/bash mark="" # 连接符号 prefix="" # 前缀 base="test" # 默认字符串 suffix="" # 后缀 upper=off # 是否大写 # 显示声明一下这是个数组变量,其实没有必要 declare -a names # 需要格式化输出的所有原始字符串 # 打印的帮助信息 help_str=" 参数说明: -h,--help: 打印帮助信息 -m,--mark [连接符]: 使用连接符,默认是下划线(_),可以指定 -a,--after string: 添加后缀 -b,--befor string: 添加前缀 -s,--string string: 指定中间的字符串,默认是“test” -u,--upper: 全大写输出 " # 解析命令行参数 getopt_cmd=$(getopt -o m::ha:b:s:u --long mark::,help,after:,befor:,string:,upper -n $(basename $0) -- "[email?protected]") [ $? -ne 0 ] && exit 1 eval set -- "$getopt_cmd" # 解析选项 while [ -n "$1" ] do case "$1" in -m|--mark) case "$2" in "") mark="_" shift ;; *) mark="$2" shift ;; esac ;; -h|--help) echo -e "$help_str" exit ;; -a|--after) suffix="$2" shift ;; -b|--befor) prefix="$2" shift ;; -s|--string) base="$2" shift ;; -u|--upper) upper=on ;; --) shift break ;; *) echo "$1 is not an option" exit 1 ;; # 发现未知参数,直接退出 esac shift done # 解析参数 while [ -n "$1" ] do names=("${names[@]}" "$1") shift done names[0]=${names[0]:-$base} for name in "${names[@]}" do # 添加前缀和后缀 output="${prefix:+${prefix}${mark}}${name}${suffix:+${mark}${suffix}}" # 判断是否要全大写输出 if [ $upper = on ] then output=${output^^} fi # 输出结果 echo "$output" done $ 验证效果: $ ./format.sh -a after -b befor VALUE1 "VALUE2 VALUE3" VALUE4 beforVALUE1after beforVALUE2 VALUE3after beforVALUE4after $ ./format.sh -a after -b befor --mark befor_test_after $ ./format.sh -a after -b befor --mark="||" -u BEFOR||TEST||AFTER $ ./format.sh -a after -b befor --mark="||" -u --help 参数说明: -h,--upper: 全大写输出 $ 有getopt就够用了。顺便再简单讲下getopts。 getopts 命令getopts是另一个解析命令行参数的工具。它是Bash的内部命令。
不支持长选项:
getopt 是将选项和参数处理后只生成一个输出。我们还要用 set 来完成传递的工作。 基本用法getopts 会使用到一下3个变量: getopts 命令的基本语法: getopts 选项字符串 名称 [参数] 选项字符串(OPTSTRING):getopts 会有哪些选项,哪些是有参数的(选项后有冒号) getopts 不会移动变量。在处理完所有的选项后,命令就会停止,并将参数留给我们来继续处理。此时可以先用shit命令配合OPTIND的值来移动到第一个参数的位置: shift $[ $OPTIND - 1 ] 错误报告模式getopts命令支持两种错误报告模式:
对于产品中的脚本,推荐使用抑制错误报告模式。 详细错误报告模式 抑制错误报告模式 示例代码这里使用抑制错误报告模式,所以需要自己分析并且报告解析错误。都在代码里了: $ cat say_hello.sh #!/bin/bash defaultname="nobody" # 默认的名字 declare -a names # 存放名字的数组 hello="hello" # 打招呼的用语 end="!" # 结束的内容 tittle=off # 是否首字母大写 # 解析选项 while getopts :n:h:e:t opt do case "$opt" in n) defaultname="$OPTARG" ;; h) hello="$OPTARG" ;; e) end="$OPTARG" ;; t) tittle=on ;; :) # 没有为需要参数的选项指定参数 echo "This option -$OPTARG requires an argument." exit 1 ;; ?) # 发现了无效的选项 echo "-$OPTARG is not an option" exit 2 ;; esac done # 解析参数 shift $[ $OPTIND -1 ] # 移动到第一个参数的位置 # 这次用for循环遍历 for arg in "[email?protected]" do names=("${names[@]}" "$arg") done names[0]=${names[0]:-$defaultname} for name in "${names[@]}" do [ "$tittle" = on ] && output="${hello^} ${name^} $end" || output="$hello $name $end" echo "${output}" done $ 验证执行如下: $ ./say_hello.sh hello nobody ! $ ./say_hello.sh -n adam hello adam ! $ ./say_hello.sh -n adam -h hi -e. -t Hi Adam . $ ./say_hello.sh -h hi -e. -t adam bob clark Hi Adam . Hi Bob . Hi Clark . $ ./say_hello.sh -a -h hi -e. -t adam bob clark -a is not an option $ ./say_hello.sh -h This option -h requires an argument. $ 选项和参数不能混排: $ ./say_hello.sh adam hello adam ! $ ./say_hello.sh adam -t hello adam ! hello -t ! $ 支持双破折线: $ ./say_hello.sh -t adam Hello Adam ! $ ./say_hello.sh -t -- adam Hello Adam ! $ 比较下来,使用起来会比getopt方便很多,不过功能上也差了很多,可选参数(双冒号::)应该也是不支持的。另外,如果熟悉getopt的话,每一步的操作都是自己的代码控制的。而getopts就简化了很多地方,比如不会调用shift移动变量。 将选项标准化
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |