Bash:为什么在解析命令行参数的脚本中使用 eval 和 shift?

Bash:为什么在解析命令行参数的脚本中使用 eval 和 shift?

当我在寻找这个答案时https://stackoverflow.com/a/11065196/4706711为了弄清楚如何使用参数,例如--something-s关于答案脚本提出的一些问题:

#!/bin/bash
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

首先,shift以下行中的程序执行什么操作:

        -a|--a-long) echo "Option a" ; shift ;;

eval那么使用下面一行命令的目的是什么:

eval set -- "$TEMP"

我尝试评论上面提到的脚本中的行,得到以下响应:

$ ./getOptExample2.sh  -a 10 -b 20 --a-long 40 -charem --c-long=echi
Param: -a
Option a
Param: 10
Internal error!

但如果我取消注释它就会像魅力一样运行:

Option a
Option b, argument `20'
Option a
Option c, argument `harem'
Option c, argument `echi'
Remaining arguments:
--> `10'
--> `40'

答案1

解析选项时要做的许多事情之一getopt是重新排列参数,以便将非选项参数放在最后,并将组合的短选项分开。从man getopt:

Output is generated for each element described in the previous section.
Output is done in the same order as the elements are specified  in  the
input,  except  for  non-option  parameters.   Output  can  be  done in
compatible (unquoted) mode, or in such way that  whitespace  and  other
special  characters  within  arguments  and  non-option  parameters are
preserved (see QUOTING).  When the output is  processed  in  the  shell
script,  it  will  seem to be composed of distinct elements that can be
processed one by  one  (by  using  the  shift  command  in  most  shell
languages).

[...]

Normally, no  non-option  parameters  output  is  generated  until  all
options  and  their  arguments  have  been  generated.   Then  '--'  is
generated as a single parameter, and after it the non-option parameters
in  the  order  they were found, each as a separate parameter.

这种效果反映在您的代码中,其中选项处理循环假设所有选项参数(包括选项参数)首先出现,并且单独出现,最后后面跟着非选项参数。

因此,TEMP包含重新排列、引用、分割的选项,并使用eval set使它们成为脚本参数。

为什么eval?您需要一种方法来安全地将 的输出转换getopt为参数。这意味着安全地处理特殊字符,如空格、、'"引号)*等。为此,getopt 请在输出中转义它们以供 shell 解释。如果没有eval,唯一的选择是set $TEMP,但是您只能通过字段分割和通配符来实现,而不是 shell 的完整解析能力。

假设你有两个论点。仅使用字段分割就无法将这两个单词作为单独的单词而不另外限制参数中可用的字符(例如,假设您将 IFS 设置为:,那么您就不能:在参数中使用它们)。因此,您需要能够转义这些字符并让 shell 解释该转义,这就是eval需要的原因。除非出现重大错误getopt,否则应该是安全的。


至于shift,它做了它一贯做的事情:删除第一个参数,并且转移所有参数(以便$2现在的内容$1)。这样就消除了已经处理过的参数,这样,在这个循环之后,只剩下非选项参数,你可以方便地使用$@而不必担心选项。

答案2

接下来,在下面的行中使用 eval 命令的目的是什么:

eval set -- "$TEMP"

util-linux 版本getopt生成的输出可用作 shell 的输入。它用引号将包含空格的字符串括起来,并处理文字引号和其他特殊字符的转义。

例如

$ getopt -o a:b -- -a 'foo bar' -b "single'quotes'here"
 -a 'foo bar' -b -- 'single'\''quotes'\''here'

不会根据普通扩展的结果处理引号,但需要进行整轮解析。就是这样eval

如果输出分配给$tmp,则在 之后eval set -- "$tmp",位置参数$1, $2, ... 包含-a, foo bar -b -- single'quotes'here, 并且在循环中相对容易处理。

仅使用set -- $tmp会将位置参数设置为-a'foobar'等...,这不是您想要的。 (如果其中一个参数是例如,你也会在顶部得到通配符*。)

问题类似于我们如何运行存储在变量中的命令?,这两种情况都涉及任意字符串列表。


请注意,该行为getopt请注意,产生 shell 引用输出特定于 util-linux 版本它的。其他系统通常有一个getopt被使用的没有 eval,并且它很高兴地打破了包含空格或看起来像通配符的参数。与此一样,无法从输出中判断出foo bar应该是单个参数。

$ getopt a:b -a 'foo bar' -b "single'quotes'here"
 -a foo bar -b -- single'quotes'here

使用时getopt,请确保先使用-T/--test选项,看看您是否有可以处理任意字符串的安全版本。

答案3

当脚本向您提供错误时,该脚本可以正常工作-a 10。该-a选项在此脚本中不需要参数。你应该只使用-a.

手册页中描述的转变如下:

shift [n]
              The positional parameters from n+1 ... are renamed to $1 ....  Parameters represented by the numbers $# down to $#-n+1 are unset.  n must be a non-negative number less than or equal to $#.  If  n  is
              0,  no  parameters are changed.  If n is not given, it is assumed to be 1.  If n is greater than $#, the positional parameters are not changed.  The return status is greater than zero if n is greater
              than $# or less than zero; otherwise 0.

所以基本上它会删除 -a 并移动剩余的参数,因此第二个参数在下一个周期中将为 $1 。

--手册页中也有描述:

 --        A -- signals the end of options and disables further option processing.  Any arguments after the -- are treated as filenames and arguments.  An argument of - is equivalent to --.

相关内容