为什么OPTIND弄乱了我的位置参数? [英] Why is OPTIND messing my positional params?

查看:84
本文介绍了为什么OPTIND弄乱了我的位置参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个功能:

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

如果使用-i调用,则应使用区分大小写的grep.但是当它放置时,将-i放置在search string位置,将search string放置在位置:

It should use grep case sensitive, if it is invoke with -i. But when it is, the it put -i is in place of search string and search string is in place of somefile:

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

在调用中,grep将$1(应为搜索字符串)作为-i,因此搜索字符串代替了file,因此不会调用(请注意.正在等待文件或标准输入-就像grep没有指定文件一样).我认为$((OPTIND-1))会根据此解释shell命令:shift $((($ optind-1)),但不这样做. 1)有人可以解释吗? +在我的情况下,对$OPTIND的很少解释也是很好的. 2)最后一个问题:为什么grep失败时|| exit 1 |不会在另一个管道之前不退出?

In invocation, the grep takes the $1 (which should be search string), as -i, so the search string is in place of file and therefor not invokes (respect. waiting for file or stdin - as grep does without file specified). I thought the $((OPTIND-1)) would shift the one option out according to this Explain the shell command: shift $(($optind - 1)) but it does not. 1) Can someone explain? + little explanation of the $OPTIND in my case would be good as well. 2) last question: why does || exit 1 | does not exit before another pipe when grep fail?

推荐答案

问题1,getopts问题:

正如我在评论中所说,您需要将OPTINDopt设置为该函数的本地变量,因此它不会继承该函数先前运行的值.要了解为什么会出现这种情况,让我从您的原始功能(从问题的第一个版本开始)开始,并以echo命令的形式添加一些工具,以显示运行过程中的变化:

Question 1, getopts problems:

As I said in a comment, you need to make OPTIND and opt local to the function, so it doesn't inherit values from previous runs of the function. To understand why this is, let me start with your original function (from the first version of your question), and add some instrumentation in the form of echo commands to show how things change as it runs:

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} "$1" "$2" || exit 1 | sed -E -n 's/^([0-9]+)/\1/p' | xargs -I{} vim +"{}" "$2";
}

...并尝试运行该命令,首先不带-i标志:

...and try running that, first with no -i flag:

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

效果很好!解析后,opt为空(应为空),并且"somesearch"和"somefile"都保留在参数列表中,以传递给grep.

And it worked fine! After parsing, opt is empty (as it should be), and both "somesearch" and "somefile" remain in the argument list to be passed to grep.

在继续之前,我应该对OPTIND进行一些解释. getopts被设计为可重复运行以遍历标志(又称选项)参数,而OPTIND是它跟踪处理参数列表的位置的一部分.特别是,它是 next 参数的编号,需要检查该参数以查看它是否是一个标志(如果存在,则进行处理).在这种情况下,它从1开始(即$1是要检查的下一个arg),并停留在该位置,因为$1是常规参数,而不是标志.

I should explain a little about OPTIND, though, before going on. getopts is designed to be run repeatedly to iterate through the flag (aka option) arguments, and OPTIND is part of how it keeps track of where it is in processing the argument list. In particular, it's the number of the next argument that it needs to examine to see if it's a flag (and process it if it is). In this case, it start off at 1 (i.e. $1 is the next arg to examine), and it stays there because $1 is a regular argument, not a flag.

顺便说一句,如果您像往常一样在处理后执行了shift $((OPTIND-1)),它将执行shift 0,这将使arg列表中的所有零标志参数成为可能.就像应该的那样. (另一方面,如果您有一个循环并将shift放入循环内,则会从getopts下更改arg列表,从而导致它无法跟踪其位置并感到非常困惑.这就是您为什么这样做的原因.将shift 放在之后.)

BTW, if you'd done shift $((OPTIND-1)) after processing as usual, it'd do shift 0, which would all zero flag arguments from the arg list. Just as it should. (On the other hand, if you had a loop and put shift inside the loop, it'd change the arg list out from under getopts, causing it to lose track of its place and get very confused. That's why you put the shift after the loop.)

好吧,让我们尝试使用一个实际的标志:

Ok, let's try it with an actual flag:

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

Again, it worked properly! It parsed the -i, set opt appropriately, incremented OPTIND to 2 so if you'd had a loop it would've examined the second argument, found it's a regular argument, and stopped the loop. And then shift $((OPTIND-1)) shifted off the one flag argument, leaving the non-flag ones to be passed to grep.

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

Let's try it again, with the same flag:

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

糟糕,现在一切都变糟了,这是因为它从上次运行中继承了OPTINDopt. OPTIND为2表示getopts已经对其进行了检查,而不必再次进行处理.它查看$2,发现它不是以-开头,因此它不是标志,因此它返回false,并且if不会运行,并且flag参数不会移开.同时,从上次运行开始,opt仍设置为"-i".

Oops, now it's all gone screwy, and it's because it inherited OPTIND and opt from the previous run. OPTIND being 2 tells getopts that it's already examined $1 and doesn't have to process it again; it looks at $2, sees it doesn't start with - so it isn't a flag, so it returns false, and the if doesn't run and the flag argument doesn't get shifted away. Meanwhile, opt is still set to "-i" from the last run.

getopts不适用于您的原因.为了证明这一点,让我们修改函数以使两个变量都位于本地:

That is why getopts hasn't been working right for you. To prove it, let's modify the function to make both variables local:

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} "$1" "$2" || exit 1 | sed -E -n 's/^([0-9]+)/\1/p' | xargs -I{} vim +"{}" "$2";
}

并尝试一下:

$ 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(其中没有继承之前的假值),并将该标志移出参数列表.

Now it starts off a little weird because OPTIND is blank instead of 1, but that's not actually a problem because getopts assumes it should start at 1. So it parses the argument, sets opt (which didn't inherit a bogus value from before), and shifts the flag out of the argument list.

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

There is a problem, though. Suppose we pass an illegal (/unsupported) flag:

$ 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处理了以-开头的参数,因此它打印了一个错误,但继续执行,并在变量i设置为?"的情况下返回了true.表示有问题.您的系统没有检查,只是假设它必须是-i.

Oops again. Since getopts processed an argument starting with -, it printed an error but went ahead and returned true with the variable i set to "?" to indicate there was a problem. Your system didn't check that, it just assumed it must be -i.

现在,让我向您展示标准(推荐)版本,其中带有while循环,并在标志上带有case,带有错误处理程序.我还自由地从行尾删除单个分号,因为它们在shell中是无用的:

Now, let me show you the standard (recommended) version, with a while loop and a case on the flag, with an error handler. I've also taken the liberty of removing single semicolons from the end of lines, 'cause they're useless in 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} "$1" "$2" || exit 1 | sed -E -n 's/^([0-9]+)/\1/p' | xargs -I{} vim +"{}" "$2"
}

并运行它:

$ 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

...即使重复运行,解析也能按预期进行.检查错误处理:

...Parsing works as expected, even with repeated runs. Check the error handling:

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

由于有一个循环,它可以处理多个标志(即使只有一个已定义的标志):

And since there's a loop, it handles multiple flags (even if there's only one defined flag):

$ 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来纾困,但是仅此而已.

Now, you might complain that that's a lot of code for such a simple task (just one possible flag!), and you'd be right. But it's basically boilerplate; you don't have to write that whole thing every time, just copy a standard example, fill in the options string and cases to handle them, and that's pretty much it. If it weren't in a function you wouldn't have the local command, and you'd use exit 1 instead of return 1 to bail out, but that's about it.

如果您真的希望它简单,只需使用if [ "$1" = "-i" ],而不必担心使用getopts的复杂性.

If you really want it to be simple, just use if [ "$1" = "-i" ], and don't get involved with the complexities of using getopts.

这种方法实际上存在三个问题:首先,要退出功能,请使用return而不是exit.

There are actually three problems with that approach: first, to exit a function you use return instead of exit.

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

Second, the shell parses pipes with higher precedence than || so the command was treated as:

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

而不是

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

第三,也是最重要的是,管道的元素在子流程中运行.对于exitreturn之类的shell命令,这意味着它们在子shell中运行,并且在子shell中运行exitreturn(或break或...)对父级没有影响.外壳程序(即运行该功能的外壳程序).这意味着您无法在管道中做任何事情来使函数直接返回.

Third, and most importantly, the elements of a pipeline run in subprocesses. For shell commands like exit and return, that means they run in subshells, and running exit or return (or break or ...) in a subshell doesn't have that effect on the parent shell (i.e the one running the function). That means there's nothing you can do within the pipeline to make the function return directly.

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

In this case, I think your best option is something like:

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

这篇关于为什么OPTIND弄乱了我的位置参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆