bash如何在处理后从参数中删除选项

bash如何在处理后从参数中删除选项

我记得在某个地方看到过一个bash脚本,使用caseshift遍历位置参数列表,在遇到参数时解析带有参数的标志和选项,并在解析后删除它们以仅留下裸露的参数,这些参数稍后由其余的参数处理脚本。

例如,在解析 的命令行时cp -R file1 -t /mybackup file2 -f,它会首先遍历参数,识别出用户已请求下降到目录-R,指定目标-t /mybackup并强制复制-f,然后从参数列表中删除这些参数,留下file1 file2作为剩余参数处理的程序。

但我似乎无法记住/找出我每次看到的任何脚本。我只是希望能够做到这一点。我一直在谷歌搜索各个网站并附加我检查过的相关页面的列表。

该网站上的一个问题专门询问了“与顺序无关的选项”,但单个答案和它所复制的问题的答案都没有考虑像上面这样的情况,其中选项与普通参数混合,我认为这是此人特别提及的原因与顺序无关选项。

由于bash的内置函数getopts似乎在第一个非选项参数处停止,因此它似乎不足以作为解决方案。这就是为什么 Wooledge BashFAQ 页面(见下文)解释了如何重新排列参数。但我想避免创建多个数组,以防参数列表很长。

由于shift不支持从参数列表中间弹出单个参数,因此我不确定什么是实现我所要求的内容的直接方法。

我想听听是否有人有任何解决方案可以从参数列表中间删除参数而不创建全新的数组。

我已经看过的页面:

答案1

POSIXly,选项的解析应该在--第一个非选项(或非选项参数)参数处停止,以先到者为准。所以在

cp -R file1 -t /mybackup file2 -f

那是在file1,所以cp应该递归地将所有file1-t/mybackup和复制file2-f目录中。

然而, GNU getopt(3)(GNUcp用于解析选项(这里您使用的是 GNU,cp因为您使用的是 GNU 特定的-t选项)),除非$POSIXLY_CORRECT设置了环境变量,否则接受参数后的选项。所以它实际上相当于 POSIX 选项样式解析:

cp -R -t /mybackup -f -- file1 file2

getopts内置的 shell,即使在 GNU shell ( ) 中也只bash处理 POSIX 风格。它也不支持长选项或带有可选参数的选项。

如果您想像 GNU 一样解析选项cp,则需要使用 GNU getopt(3)API。为此,如果在 Linux 上,您可以使用getopt(util-linuxgetopt命令的增强版本也被移植到其他一些 Unices,例如自由BSD)。

getopt将以规范的方式重新排列选项,使您可以通过循环简单地解析它while/case

$ getopt -n "$0" -o t:Rf -- -Rf file1 -t/mybackup file2
 -R -f -t '/mybackup' -- 'file1' 'file2'

您通常将其用作:

parsed_options=$(
  getopt -n "$0" -o t:Rf -- "$@"
) || exit
eval "set -- $parsed_options"
while [ "$#" -gt 0 ]; do
  case $1 in
    (-[Rf]) shift;;
    (-t) shift 2;;
    (--) shift; break;;
    (*) exit 1 # should never be reached.
  esac
done
echo "Now, the arguments are $*"

另请注意,它将getopt以与 GNU 相同的方式解析选项cp。特别是,它支持长选项(并以缩写形式输入它们)并尊重$POSIXLY_CORRECT环境变量(设置时会禁用对参数后选项的支持),就像 GNU 一样cp

请注意,使用 gdb 并打印接收到的参数getopt_long()可以帮助构建参数getopt(1)

(gdb) bt
#0  getopt_long (argc=2, argv=0x7fffffffdae8, options=0x4171a6 "abdfHilLnprst:uvxPRS:T", long_options=0x417c20, opt_index=0x0) at getopt1.c:64
(gdb) set print pretty on
(gdb) p *long_options@40
$10 = {{
    name = 0x4171fb "archive",
    has_arg = 0,
    flag = 0x0,
    val = 97
  }, {
    name = 0x417203 "attributes-only",
[...]

然后你可以使用getopt如下:

getopt -n cp -o abdfHilLnprst:uvxPRS:T -l archive... -- "$@"

请记住,GNUcp支持的选项列表可能会从一个版本更改为下一个版本,并且getopt无法检查您是否向该--sparse选项传递了合法值。

答案2

因此,每次getopts处理参数时,它不会期望将 shell 变量设置$OPTIND为参数列表中应处理的下一个数字,并返回 0 以外的值。如果$OPTIND设置为值 1,getopts则 POSIX 指定接受新的参数列表。因此,这只是监视getopts返回,为任何失败的返回保存增量计数器加号$OPTIND,移走失败的参数,并重置$OPTIND每个失败的尝试。您可以像这样使用它opts "$@"- 尽管您想要自定义case循环或将其保存到变量中并将该部分更改为eval $case.

opts() while getopts :Rt:f opt || {
             until command shift "$OPTIND" || return 0
                   args=$args' "${'"$((a=${a:-0}+$OPTIND))"'}"'
                   [ -n "${1#"${1#-}"}" ]
             do OPTIND=1; done 2>/dev/null; continue
};     do case "$opt"  in
             R) Rflag=1      ;;
             t) tflag=1      ;
                targ=$OPTARG ;;
             f) fflag=1      ;;
       esac; done

运行时它设置$args为每个getopts未处理的参数...所以...

set -- -R file1 -t /mybackup file2 -f
args= opts "$@"; eval "set -- $args"
printf %s\\n "$args"
printf %s\\n "$@"         
printf %s\\n "$Rflag" "$tflag" "$fflag" "$targ"

输出

 "${2}" "${5}"
file1
file2
1
1
1
/mybackup

这适用于bash, dash, zsh, ksh93, mksh... 好吧,我当时就放弃了尝试。在每个 shell 中,它还包含$[Rtf]flag$targ。关键是所有getopts不想处理的参数的数字都保留了。

更改选项样式也没有什么区别。它的工作方式类似于-Rf -t/mybackup-R -f -t /mybackup。它可以在列表的中间、列表的末尾或列表的开头工作......

尽管如此,最好的办法只是在你的参数列表上粘贴一个--for 选项结尾,然后shift "$(($OPTIND-1))"在处理运行结束时执行getopts。这样你就可以删除所有已处理的参数保留参数列表的尾部。

我喜欢做的一件事是将长期权翻译成短期权——我以非常相似的方式做到这一点,这就是为什么这个答案很容易得到——我跑getopts

i=0
until [ "$((i=$i+1))" -gt "$#" ]
do case "$1"                   in
--Recursive) set -- "$@" "-R"  ;;
--file)      set -- "$@" "-f"  ;;
--target)    set -- "$@" "-t"  ;;
*)           set -- "$@" "$1"  ;;
esac; shift; done

相关内容