能否使用getopts解析bash脚本参数的子集并保留其余部分不变?

4

我在bash脚本中使用getopts来解析参数。我想要做两件事:

  • "$@"中移除已处理的选项
  • 将未处理的选项保留在"$@"

考虑命令行:

$ foo -a val_a -b val_b -c -d -e -f val_f positional_l positional_2 ...

foo使用getopts来解析由'b:c'定义的选项时,需要在后面将"$@"留下。

`-a val_a -d -e -f val_f positional_l positional_2 ...`

我需要做两件事:

  • 解析可能给出的一些选项
  • 保留所有其他选项不变

这是因为foo必须使用它识别的选项来确定另一个脚本bar,并将剩余的"@"传递给它。

通常情况下,getopts在遇到未识别的选项时会停止,但我需要它继续(直到任何--)。我需要处理和删除它识别的选项,并保留它不识别的选项。


我尝试使用--foo选项和bar选项之间解决我的问题,但如果--后面的文本以-开头,getopts似乎会抛出错误(我尝试过但无法转义连字符)。

无论如何,我不想使用--,因为我希望bar的存在对foo的调用者来说是有效透明的,并且我希望foo的调用者能够以任何顺序提供选项。

我还尝试在foo中列出所有的bar选项(即使用'a:b:cdef:'作为optstring),但不进行处理,但我需要删除它们在"$@"中的位置。我无法弄清楚如何做到这一点(shift不允许指定位置)。


我可以手动重构一个新的选项列表(请参阅我的答案),但我想知道是否有更好的方法来解决这个问题。


1
一个(看似不可逾越的)问题:如果你看到 -a -b 3,那么这是一个未知选项 -a 后面没有参数,接着是预期的 -b 选项,还是一个带有参数 -b 的未知选项 -a,后面跟着一个位置参数 3 - chepner
@chepner:说得好。另一个问题是:-ab选项是-a和选项参数b,还是要解释为-a-b的选项组? - mklement0
1
那看起来更加难以克服 :) - chepner
2个回答

3
您可以像这个例子一样手动重建选项列表,该示例处理-b-c选项,并将其余的内容保持不变。
#!/bin/bash
while getopts ":a:b:cdef:" opt
do
  case "${opt}" in
    b) file="$OPTARG" ;;
    c) ;;
    *) opts+=("-${opt}"); [[ -n "$OPTARG" ]] && opts+=("$OPTARG") ;;
  esac
done
shift "$((OPTIND-1))"
./$file "${opts[@]}" "$@"

所以
./foo -a 'foo bar' -b bar -c -d -e -f baz one two 'three and four' five

如果将b选项的参数设置为bar,则会激活bar函数。

./bar -a 'foo bar' -d -e -f baz one two 'three and four' five

这种解决方案的缺点在于optstring必须包括传递的选项(即":a:b:cdef:"而不是更可取的":b:c")。
用重构后的参数列表替换原始列表可以这样做:
set -- "${opts[@]}" "$@"

这将使"$@"包含未经处理的参数,如问题中所指定的那样。

3
尝试以下方法,它只需要预先知道脚本自己的选项即可:
#!/usr/bin/env bash

passThru=() # init. pass-through array
while getopts ':cb:' opt; do # look only for *own* options
  case "$opt" in
    b)
      file="$OPTARG";;
    c) ;;
    *) # pass-thru option, possibly followed by an argument
      passThru+=( "-$OPTARG" ) # add to pass-through array
      # see if the next arg is an option, and, if not,
      # add it to the pass-through array and skip it
      if [[ ${@: OPTIND:1} != -* ]]; then
        passThru+=( "${@: OPTIND:1}" )
        (( ++OPTIND ))
      fi
      ;;
  esac
done
shift $((OPTIND - 1))
passThru+=( "$@" )  # append remaining args. (operands), if any

./"$file" "${passThru[@]}"

注意事项: 有两种歧义无法通过此方法解决:

  • 对于带有选项参数的传递选项,只有当参数没有直接附加到选项时,此方法才有效。
    例如,-a val_a有效,但-aval_a无效(在getopts参数中没有a:,这将被解释为一个选项,并将其转换为多个选项-a-v-a-l-_-a)。

  • 正如chepner在问题的评论中指出的那样,-a -b可以是选项-a和选项参数-b(只是看起来像一个选项本身),或者它可以是不同的选项-a-b;上述方法将执行后者。

为了解决这些不确定性,你必须坚持你自己的方法,但这样做的缺点是需要提前了解所有可能的传递选项。

1
与我之前写的代码类似,除了通配符情况。我认为这些警告相当难以理解且可以忽略,以获得不需要先前了解传递参数的好处。我没有意识到您可以修改getopts变量。好的解决方案。 - starfry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接