问题一,getopts问题:
正如我在评论中所说,您需要将 OPTIND 和 opt 设置为函数的局部变量,因此它不会继承函数之前运行的值。要理解为什么会这样,让我从您的原始函数开始(从您的问题的第一个版本开始),并以 echo 命令的形式添加一些工具,以显示运行时事情的变化:
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 标志:
$ sgrep 'somesearch' 'somefile'
Starting sgrep, OPTIND='1', opt='', args=somesearch somefile
Done parsing, OPTIND='1', opt='', args=somesearch somefile
而且效果很好!解析后,opt 为空(应该是),“somesearch”和“somefile”都保留在要传递给grep 的参数列表中。
不过,在继续之前,我应该稍微解释一下OPTIND。 getopts 旨在重复运行以迭代标志(又名选项)参数,OPTIND 是它跟踪处理参数列表的位置的一部分。特别是,它是 next 参数的编号,它需要检查它是否是一个标志(如果是,则处理它)。在这种情况下,它从 1 开始(即 $1 是下一个要检查的参数),并且它一直留在那里,因为 $1 是一个常规参数,而不是一个标志。
顺便说一句,如果您在照常处理后完成了shift $((OPTIND-1)),它会执行shift 0,这将所有零标记参数列表中的参数。正如它应该的那样。 (另一方面,如果你有一个循环并将shift 放入循环中,它会将arg 列表从getopts 下更改出来,导致它失去对它的位置的跟踪并且变得非常混乱。这就是为什么你将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
糟糕,现在一切都搞砸了,这是因为它从上一次运行中继承了 OPTIND 和 opt。 OPTIND 是 2 告诉 getopts 它已经检查过 $1 并且不必再次处理它;它查看$2,发现它不是以- 开头的,所以它不是标志,所以它返回false,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} "$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(其中 没有从以前继承一个虚假的值),并将标志移出参数列表。
但是有一个问题。假设我们传递了一个非法(/unsupported)标志:
$ 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 处理了一个以- 开头的参数,它打印了一个错误但继续返回true,变量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} "$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
...解析按预期工作,即使重复运行。检查错误处理:
$ 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 [ "$1" = "-i" ],不要涉及使用getopts 的复杂性。
问题2,为什么grep失败时|| exit 1 |没有在另一个管道之前退出?:
这种方法实际上存在三个问题:首先,要退出使用return 而不是exit 的函数。
其次,shell解析管道的优先级高于||,因此命令被视为:
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"
第三,也是最重要的一点,管道的元素在子流程中运行。对于像exit 和return 这样的shell 命令,这意味着它们在子shell 中运行,而在子shell 中运行exit 或return(或break 或...)对父shell 没有这种影响shell(即运行函数的那个)。这意味着您无法在管道中执行任何操作来使函数直接返回。
在这种情况下,我认为您最好的选择是:
grep ... | otherstuff
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
return 1
fi