为什么 OPTIND 弄乱了我的位置参数?

Why is OPTIND messing my positional params?

我有这个功能:

    sgrep () 
{ 
    local OPTIND;
    if getopts i o; then
        grep --color=auto -P -in "" "";
        shift $((OPTIND-1));
    else
        grep --color=auto -P -n "" "";
    fi | sed -E -n 's/^([0-9]+).*//p' | xargs -I{} vim +"{}" "";
    stty sane
}

如果使用 -i 调用,则应使用区分大小写的 grep。但是当它是时,它把 -i 代替 search string 并且 search string 代替 somefile:

$ set -x
$ sgrep 'somesearch' 'somefile'
---#output---
+ sgrep -i 'somesearch' 'somefile'
+ local OPTIND
+ sed -E -n 's/^([0-9]+).*//p'
+ getopts i o
+ grep --color=auto -P -in -i 'somesearch'

在调用中,grep 将 </code>(应该是搜索字符串)作为 <code>-i,因此搜索字符串代替了 file,因此不会调用 ( respect.waiting for file or stdin - as grep does without file specified)。我认为 $((OPTIND-1)) 会根据此 Explain the shell command: shift $(($optind - 1)) 移出一个选项,但事实并非如此。 1)有人可以解释吗? + 在我的案例中对 $OPTIND 的一些解释也很好。 2) 最后一个问题:当 grep 失败时,为什么 || exit 1 | 没有在另一个管道之前退出?

我已经将位置参数改大了:

sgrep () 
    { 
        local OPTIND;
        if getopts i o; then
            grep --color=auto -P -in "" "";
            shift $((OPTIND-1));
        else
            grep --color=auto -P -n "" "";
        fi | sed -E -n 's/^([0-9]+).*//p' | xargs -I{} vim +"{}" "";
        stty sane
    }

哪个有效,但我不喜欢它。在一个分支中,由于选项的原因,我必须使用更大的头寸,但在另一个没有使用选项的分支中,头寸不会改变。很乱

我试过在 then 之后立即添加 shift $((OPTIND-1)) 但无济于事。

问题 1,getopts 个问题:

正如我在评论中所说,您需要使 OPTINDopt 成为函数的局部变量,因此它不会继承函数先前 运行 的值.要理解这是为什么,让我从您的原始功能开始(从您问题的第一个版本开始),并以 echo 命令的形式添加一些工具以显示事情如何随着 运行s 的变化而变化:

sgrep () 
{ 
    echo "Starting sgrep, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    if getopts "i" i; then
        opt="-i";
        shift $((OPTIND-1));
        echo "Parsed -$i flag, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    fi;
    echo "Done parsing,   OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    # grep --color=auto -P ${opt} "" "" || exit 1 | sed -E -n 's/^([0-9]+)//p' | xargs -I{} vim +"{}" "";
}

...然后尝试 运行ning,首先没有 -i 标志:

$ sgrep 'somesearch' 'somefile'
Starting sgrep, OPTIND='1', opt='', args=somesearch somefile
Done parsing,   OPTIND='1', opt='', args=somesearch somefile

而且效果很好!解析后,opt 为空(应该是),"somesearch" 和 "somefile" 都保留在要传递给 grep.

的参数列表中

不过,在继续之前,我应该稍微解释一下 OPTINDgetopts 被设计为 运行 重复遍历标志(又名选项)参数,而 OPTIND 是它如何跟踪它在处理参数列表中的位置的一部分。特别是,它是 next 参数的编号,它需要检查它是否是一个标志(如果是则处理它)。在这种情况下,它从 1 开始(即 </code> 是下一个要检查的参数),并且它保持在那里,因为 <code> 是一个常规参数,而不是一个标志。

顺便说一句,如果您像往常一样在处理后完成 shift $((OPTIND-1)),它会执行 shift 0,这会将 arg 列表中的所有零标志参数。就像它应该的那样。 (另一方面,如果你有一个循环并将 shift 放在循环中,它会从 getopts 下更改 arg 列表,导致它失去对它的位置的跟踪并变得非常困惑. 这就是为什么你把 shift 放在 循环之后。)

好的,让我们用实际的标志来试试:

$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='1', opt='', args=-i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile

再一次,它工作正常!它解析 -i,适当地设置 opt,将 OPTIND 增加到 2,所以如果你有一个循环,它会检查第二个参数,发现它是一个常规参数,然后停止循环。然后 shift $((OPTIND-1)) 移出一个标志参数,将非标志参数传递给 grep.

让我们再试一次,使用相同的标志:

$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='2', opt='-i', args=-i somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=-i somesearch somefile

哎呀,现在都乱七八糟了,因为它继承了之前运行的OPTINDoptOPTIND 为 2 告诉 getopts 它已经被检查过 </code> 并且不必再次处理它;它查看 <code>,发现它不是以 - 开头,所以它不是一个标志,所以它 return 是假的,而 if 不是 运行 并且标志参数不会被移走。同时,opt 仍然设置为上一个 运行 的“-i”。

这就是 为什么 getopts 一直不适合您的原因。为了证明这一点,让我们修改函数使两个变量都成为局部变量:

sgrep ()
{
    local OPTIND opt    # <- This is the only change here
    echo "Starting sgrep, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    if getopts "i" i; then
        opt="-i";
        shift $((OPTIND-1));
        echo "Parsed -$i flag, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    fi;
    echo "Done parsing,   OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    # grep --color=auto -P ${opt} "" "" || exit 1 | sed -E -n 's/^([0-9]+)//p' | xargs -I{} vim +"{}" "";
}

并尝试一下:

$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile
$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile

现在它开始有点奇怪,因为 OPTIND 是空白而不是 1,但这实际上不是问题,因为 getopts 假设它应该从 1 开始。所以它解析参数,设置opt没有继承之前的虚假值),并将标志移出参数列表。

但是有一个问题。假设我们传递了一个非法(/不受支持)的标志:

$ sgrep -k 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-k somesearch somefile
-bash: illegal option -- k
Parsed -? flag, OPTIND='2', opt='-i', args=somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile

再次糟糕。由于 getopts 处理了一个以 - 开头的参数,它打印了一个错误但继续进行并且 return 为真,变量 i 设置为“?”表示有问题。你的系统没有检查,它只是假设它必须是 -i.

现在,让我向您展示标准(推荐)版本,在标志上有一个 while 循环和一个 case,还有一个错误处理程序。我还冒昧地从行尾删除了单个分号,因为它们在 shell:

中没有用
sgrep ()
{
    local OPTIND opt
    echo "Starting sgrep, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    while getopts "i" i; do
        case "$i" in
            i )
                opt="-$i"
                echo "Parsed -$i flag, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
                ;;
            * )
                return 1 ;;
        esac
    done
    shift $((OPTIND-1))
    echo "Done parsing,   OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    # grep --color=auto -P ${opt} "" "" || exit 1 | sed -E -n 's/^([0-9]+)//p' | xargs -I{} vim +"{}" ""
}

和运行它:

$ sgrep 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=somesearch somefile
Done parsing,   OPTIND='1', opt='', args=somesearch somefile
$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=-i somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile
$ sgrep 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=somesearch somefile
Done parsing,   OPTIND='1', opt='', args=somesearch somefile
$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=-i somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile

...解析按预期工作,即使重复 运行s。检查错误处理:

$ sgrep -k 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-k somesearch somefile
-bash: illegal option -- k

并且因为有一个循环,它处理多个标志(即使只有一个定义的标志):

$ sgrep -i -i -i -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-i -i -i -i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=-i -i -i -i somesearch somefile
Parsed -i flag, OPTIND='3', opt='-i', args=-i -i -i -i somesearch somefile
Parsed -i flag, OPTIND='4', opt='-i', args=-i -i -i -i somesearch somefile
Parsed -i flag, OPTIND='5', opt='-i', args=-i -i -i -i somesearch somefile
Done parsing,   OPTIND='5', opt='-i', args=somesearch somefile

现在,您可能会抱怨这么简单的任务(只有一个可能的标志!)有很多代码,您是对的。但它基本上是样板;您不必每次都编写整个内容,只需复制一个标准示例,填写选项字符串和案例来处理它们,就可以了。如果它不在一个函数中,你就不会有 local 命令,你会使用 exit 1 而不是 return 1 来摆脱困境,仅此而已。

如果你真的想要它简单,就用if [ "" = "-i" ],不要纠结于使用getopts.

的复杂性

问题2,为什么grep失败时|| exit 1 |没有在另一个管道之前退出?:

这种方法实际上存在三个问题:首先,要退出您使用 return 而不是 exit 的函数。

其次,shell 解析优先级高于 || 的管道,因此该命令被视为:

    grep --color=auto -P ${opt} "" ""
Logical or'ed with:
    exit 1 | sed -E -n 's/^([0-9]+)//p' | xargs -I{} vim +"{}" ""

而不是

    grep --color=auto -P ${opt} "" "" || exit 1
Piped to:
    sed -E -n 's/^([0-9]+)//p' | xargs -I{} vim +"{}" ""

第三,也是最重要的,子流程中管道的元素 运行。对于像 exitreturn 这样的 shell 命令,这意味着它们 运行 in subshells,并且 运行ning exit 或者return(或 break 或 ...)在子 shell 中对父 shell 没有这种影响(即 运行 宁功能)。这意味着您无法在管道内执行任何操作来直接创建函数 return。

在这种情况下,我认为你最好的选择是:

grep ... | otherstuff
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
    return 1
fi