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


9

我记得在某个地方看到过一个bash脚本case,该脚本使用和shift浏览位置参数列表,遇到参数时解析带有参数的标志和选项,并在解析后将其删除以仅保留裸参数,然后由其余的参数处理。脚本。

例如,在解析的命令行时cp -R file1 -t /mybackup file2 -f,它将首先遍历参数,认识到用户已请求通过进入目录,通过来-R指定目标,-t /mybackup并通过来强制复制-f,然后将其从参数列表中删除,从而该程序file1 file2作为剩余参数进行处理。

但是我似乎无法记住/找到我所看到的任何脚本。我只是想能够做到这一点。我一直在各地搜索,并附加了我检查过的相关页面列表。

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

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

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

我想听听是否有人有解决方案,可以从参数列表的中间删除参数,而无需创建一个新的数组。

我已经看过的页面:


1
一旦我需要一些复杂的东西,就从bash切换到Perl。
choroba

Answers:


9

POSIXly,对选项的解析应--在第一个非选项(或非选项参数)参数处或在第一个出现的参数处停止。所以在

cp -R file1 -t /mybackup file2 -f

那是在file1,所以cp应该递归复制所有的file1-t/mybackupfile2进入-f目录。

GNU getopt(3)无论如何(GNU cp用于分析选项(在这里你正在使用的GNU cp因为你正在使用的GNU特定-t选项)),除非$POSIXLY_CORRECT环境变量设置,接受参数后的选择。因此,它实际上等效于POSIX选项样式解析:

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

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

如果要像GNU一样解析选项cp,则需要使用GNU getopt(3)API。为此,如果在Linux上,则可以使用getoptfrom中的增强实用程序util-linuxgetopt命令的增强版本也已移植到了FreeBSD等其他Unices上)。

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... -- "$@"

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


@all:感谢您的回复。@Stephane:您使用while [ "$#" -gt 0 ]io 的原因while (($#))吗?只是为了避免宗教歧视吗?
jamadagni 2014年

是的,尽管(($#))更多是克什米尔主义
斯特凡Chazelas

1

因此,每次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

这个作品bashdashzshksh93mksh...好,我放弃了在这一点尝试。在每个壳中,它也有$[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

您的长选项转换还将转换非选项(as cp -t --file foocp -- --file foo),并且不会处理输入的缩写(--fi...)或--target=/dest语法。
斯特凡Chazelas

@StéphaneChazelas-转换很长的东西只是一个例子-很显然它的构建不是很好。我getopts用一个更简单的功能代替了它。
mikeserv

@StéphaneChazelas-请再看一次。尽管长期权评论仍然合理,但我认为第一个(及其附带的下注)不再是。另外,我认为在单个循环中工作时会opts()影响到您自己的答案opts()-一次触摸每个参数一次-并且可靠地(据我所知),可移植且没有单个subshel​​l。
mikeserv

重置OPTIND是我所谓的启动另一个getopts循环。实际上,这是解析的几个选项的命令行/集(-R-t /mybackup-f)。我将继续投票,因为它仍然很模糊,您使用的是$a未初始化的,并且args= opts...可能在许多shell(包括bash)返回args后保持不变(或设置为之前的状态opts)。
斯特凡Chazelas

@StéphaneChazelas-其中包括bash-我讨厌。我认为,如果该函数是当前的shell函数,则应保留该函数。我正在解决这些问题-因为可以可靠地使当前函数具有更多测试,以处理所有情况,甚至可以使用前面的选项标记参数,但我不同意它的混淆。如所写,这是完成我能想到的任务的最简单最直接的方法。在每种情况下您都应该失败时,test然后再执行shift几乎没有任何意义。不打算这样做。shiftreturn
mikeserv
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.