• shell命令getopt简介


      前言:近期模仿磊哥的一个命令工具,遇到了shell里的getopt命令,它可以接受长短命令参数,原命令里只接了2个参数,我模仿的命令里需要接收3个参数,且都有长短写法,模拟了半天始终无法获取正确的参数,于是开始研究getopt命令。于是有了下面的转载文章

    引用自:https://blog.csdn.net/wanglz666/article/details/44565809

    getopt简介

    以下主要翻译自man getopt。
    getopt是用来解析传入shell的命令行参数的,它可以支持如 ‘rm -r’ 中’-r’形式的参数解析。

    命令语法

    先从一个较为明了的语法开始

    getopt -o optstring -- parameters
     

    这里,getopt所带参数可以分成三个部分:

    • -o和--选项是getopt命令自身的选项
    • parameters是待解析的参数(如shell传入的参数)
    • -o选项后面的optstring用来指示解析parameters的方式

    来看一个简单的例子

    getopt -o a: -- -a para_a
     

    对应到命令格式

    optstringparameters
    a: -a para_a

    解析规则

    getopt将根据optstring从左到右解析parameters。每个parameter将被解析为短选项(如上例中的-a)、长选项、选项参数(an argument to an option)及非选项参数(a non-option parameter)。

    Tip: 这里的短选项仅指parameters中解析出的短选项,而非getopt自带的短选项'-o'

    短选项

    getopt的-o选项用来指示parameters中包含的短选项。parameters中的短选项由符号'-'和紧连其后的一个字符构成。如上述简单例子中-o后面的a:,指示parameters中可以包含一个形如-a 的短选项。

    • a:中还有一个冒号,这指示-a有一个required argument;
    • 如果是-a::,有两个冒号,则指示-a有一个optional argument;
    • 如果仅仅是-a,没有冒号,则指示-a后面不需要任何argument,就好比很多命令中用来查看版本的-v选项,就是不用带任何argument的。

    如果该选项有一个required argument,则这个argument可以紧连着写在选项之后,也可以隔一个空格。
    如果该选项有一个optional argument,则该argument必须紧连着写在选项之后。

    看例子

    • required argument的例子
    getopt -o a: -- -a para_for_a
    getopt -o a: -- -apara_for_a
     

    这两个命令的输出都是 -a 'para_for_a' --
    para_for_a成功被解析为-a的选项参数,而--后面的是非选项参数,在该例中,没有非选项参数。

    • optional argument的例子
    getopt -o a:: -- -apara_for_a
    getopt -o a:: -- -a para_for_a

    第一个命令输出 -a 'para_for_a' --
    第二个命令输出 -a '' -- 'para_for_a'

    可以看到,在optional argument的第二个命令中,如果para_for_a不紧跟在-a之后,则被当做了非选项参数。而-a选项需要的选项参数则被默认置空。

    在-o后面也可以指定多个短选项,直接写在一起就行了。

    看例子

    getopt -o ab::c: -- -a -bpara_for_b -c para_for_c

    输出结果-a -b 'para_for_b' -c 'para_for_c' --

    该例中指定了一个无参数的-a选项,一个有optional argument的-b选项,以及一个有required argument的-c选项。

    长选项

    比如说-v是短选项,而--version则是长选项。
    getopt中可以使用-l来指定长选项。-l后也可以指定多个选项,多个选项之间以逗号分隔。长选项一般以--接上长选项的名称。

    如例子

    getopt -l a-long: -- --a-long=para_for_a-long //error

    这里指定了a-long这个长选项。

    • --a-long:中还有一个冒号,这指示--a-long有一个required argument;
    • 如果是--a-long::,有两个冒号,则指示--a-long有一个optional argument;
    • 如果仅仅是--a-long,没有冒号,则指示--a-long后面不需要任何argument,就好比很多命令中用来查看版本的--version选项,就是不用带任何argument的。

    如果一个长选项有一个required argument,则其参数可以使用=连接在其后,或者空一个空格连在气候。
    如果一个长选项有一个optional argument,则其参数必须使用=连接在其后。

    不过,上面的那个例子后面注释了一个error。如果尝试执行上面的那个例子的话,可以看到,并没有出现预期中的结果。

    原因是因为getopt对-o选项的处理。参看对-o选项的说明,有这样一句

    If this option is not found, the first parameter of getopt that does not start with a `-’ (and is not an option argument) is used as the short options string.

    这句话的意思是说,如果getopt命令没有发现-o选项,则会尝试去找默认的short option string。

    如果我们尝试执行这样的命令

    getopt -l a-long: -- --a-long=para_for_a-long -a -l -o -n -g -p -r -f -z

    其实也可以写成getopt -l a-long: -- --a-long=para_for_a-long -alongprfz

    我们会得到以下的输出

    getopt: invalid option -- 'z'
    -a -l -o -n -g -p -r -f --

    具体是如何将alongprf这几个字符解析成短选项的,我暂时没有去深入了。

    解决方法是明确指定-o为空,如下

    getopt -o '' -l a-long: -- --a-long=para_for_a-long

    此时,即可得到输出

    --a-long 'para_for_a-long' --

    当初这个问题是参考的stackExchange上的Q&A

    双引号的作用

    在接下来看更具体的例子之前,我们先来看看双引号对getopt命令的作用。

    getopt -o a: -- -a para_a
    getopt -o a: -- "-a para_a"

    这两个命令的输出有细微的区别
    第一个命令是-a 'para_a' --
    第二个命令是-a ' para_a' --
    第二个比第一个多了一个空格。

    getopt -o a: -a para_a
    getopt -o a: "-a para_a"

    这两个命令区别明显
    第一个命令的输出是-- 'para_a'
    第二个命令的输出是getopt: invalid option -- ' '

    对于第一个命令的输出,貌似跟预期的也不大一样。这是因为-a是getopt本身自带的一个选项,这样para_a就被解析成了一个non-option parameter。

    对于双引号造成的区别,应该和shell的expansion有关,还不是很理解。但可以看到的是,双引号使得空格保留下来了,作为了参数的一部分,使得getopt在处理时,将-a para_a 当做了一个整体。

    Tip:这里没有使用--
    man getopt中有说明 “The second part will start at the first non-option parameter that is not an option argument, or after the first occurrence of --.”
    也就是说,如果没有--的话,则getopt会将从第一个不是用来指定选项的参数(non-option parameter) 开始,将其后的内容解释为getopt命令中的parameters部分。

    shell脚本示例

    上面了解了getopt的基本使用方法,这里展示一个在shell脚本中使用getopt的例子。
    这个例子是getopt自带的,在man getopt的EXAMPLES小节可以找到例子的路径。

    #!/bin/bash
    
    # A small example program for using the new getopt(1) program.
    # This program will only work with bash(1)
    # An similar program using the tcsh(1) script language can be found
    # as parse.tcsh
    
    # Example input and output (from the bash prompt):
    # ./parse.bash -a par1 'another arg' --c-long 'wow!*?' -cmore -b " very long "
    # Option a
    # Option c, no argument
    # Option c, argument `more'
    # Option b, argument ` very long '
    # Remaining arguments:
    # --> `par1'
    # --> `another arg'
    # --> `wow!*?'
    
    # Note that we use `"$@"' to let each command-line parameter expand to a 
    # separate word. The quotes around `$@' are essential!
    # We need TEMP as the `eval set --' would nuke the return value of getopt.
    # 上面这句话的意思是,我们需要TEMP变量是因为`eval set --'命令会覆盖掉getopt命令的返回码,
    # 这样我们就无法判断getopt命令的执行成功与否了(居然为此纠结了许久。。。)
    # 下面一句中 $@ 是shell脚本传入的参数
    TEMP=`getopt -o ab:c:: --long a-long,b-long:,c-long:: 
         -n 'example.bash' -- "$@"`
    
    if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
    
    # Note the quotes around `$TEMP': they are essential!
    eval set -- "$TEMP"
    
    while true ; do
            case "$1" in
                    -a|--a-long) echo "Option a" ; shift ;;
                    -b|--b-long) echo "Option b, argument \`$2'" ; shift 2 ;;
                    -c|--c-long) 
                            # c has an optional argument. As we are in quoted mode,
                            # an empty parameter will be generated if its optional
                            # argument is not found.
                            case "$2" in
                                    "") echo "Option c, no argument"; shift 2 ;;
                                    *)  echo "Option c, argument \`$2'" ; shift 2 ;;
                            esac ;;
                    --) shift ; break ;;
                    *) echo "Internal error!" ; exit 1 ;;
            esac
    done
    echo "Remaining arguments:"
    for arg do echo '--> '"\`$arg'" ; done

    这里对eval set和shift进行说明。

    shift命令

    较简单,即是将位置参数进行左移。

    位置参数是指shell中 $0$1等参数。

    set命令

    set -- 用来设置位置参数

    我们结合set和shift来写个例子

    set -- a b
    echo "before shift 1," '$1' "is $1"
    echo "before shift 1," '$2' "is $2"
    shift 1
    echo "after shift 1," '$1' "is $1"
    echo "after shift 1," '$2' "is $2"

    依次执行以上命令(或写在shell脚本中执行),得到的输出如下

    before shift 1, $1 is a
    before shift 1, $2 is b
    after shift 1, $1 is b
    after shift 1, $2 is

    eval

    eval是对其后的命令再次进行shell解析,比如参数替换什么的。

    在man getopt的DESCRIPTION一节的最后一段,有这么一句

    Traditional implementations of getopt(1) are unable to cope with whitespace and other (shell-specific) special characters in arguments and non-option parameters. To solve this problem, this implementation can generate quoted output which must once again be interpreted by the shell (usually by using the eval command).

    这意思就是在示例中eval命令是用来保留一些特殊字符,如空格的。

    感觉这里eval的作用和双引号有点像,然后在代码的注释中,还有两处essential。分别是说$@的双引号essential,以及eval中$TEMP的双引号essential。这里大约都与特殊符号有关吧。

    更进一步的解释可以参考Bash: Preserving Whitespace Using set and eval,这篇文章最后一点也没看明白。。。

    实际运行

    使用例子中提供的命令./parse.bash -a par1 'another arg' --c-long 'wow!*?' -cmore -b " very long ",这里面包含了很多特殊符号。

    • 两处引号均存在的输出
    Option a
    Option c, no argument
    Option c, argument `more'
    Option b, argument ` very long '
    Remaining arguments:
    --> `par1'
    --> `another arg'
    --> `wow!*?'
    • 仅去除$@的引号的输出
    Option a
    Option c, no argument
    Option c, argument `more'
    Option b, argument `very'
    Remaining arguments:
    --> `par1'
    --> `another'
    --> `arg'
    --> `wow!*?'
    --> `long'
    • 仅去除eval中$TEMP的引号的输出
    Option a
    Option c, no argument
    Option c, argument `more'
    Option b, argument ` very long '
    Remaining arguments:
    --> `par1'
    --> `another arg'
    --> `wow!*?'
    • 两处引号均去除的输出
    Option a
    Option c, no argument
    Option c, argument `more'
    Option b, argument `very'
    Remaining arguments:
    --> `par1'
    --> `another'
    --> `arg'
    --> `wow!*?'
    --> `long'

    从上面的结果中看到,如果$@的引号缺失了,会使得'another arg'" very long "的空格丢失;而$TEMP引号是否缺失,似乎并无影响。

    不是很明白。。。

    最后

    感觉getopt就是按照指定规则对参数进行重排序的过程。
    从getopt自带的示例中也可以看到,重排序后的结果被set命令设置为位置参数,再由用户自行处理。

    发现想写一个东西,往往牵扯到好多细节。
    这篇文章写到最后,依然没弄清eval和双引号的作用

  • 相关阅读:
    SpingMVC类型转换
    SpingMVC系统异常处理(二)
    JDBC 之 事务
    JDBC 基础概念
    部分实用的SQL语句
    JDBC基础学习
    手动去除集合中重复元素
    三种形式遍历集合
    java IO流 复制图片
    java IO流 之 字符流
  • 原文地址:https://www.cnblogs.com/lxhbky/p/14189189.html
Copyright © 2020-2023  润新知