为什么 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
个问题:
正如我在评论中所说,您需要使 OPTIND
和 opt
成为函数的局部变量,因此它不会继承函数先前 运行 的值.要理解这是为什么,让我从您的原始功能开始(从您问题的第一个版本开始),并以 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
.
的参数列表中
不过,在继续之前,我应该稍微解释一下 OPTIND
。 getopts
被设计为 运行 重复遍历标志(又名选项)参数,而 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
哎呀,现在都乱七八糟了,因为它继承了之前运行的OPTIND
和opt
。 OPTIND
为 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 +"{}" ""
第三,也是最重要的,子流程中管道的元素 运行。对于像 exit
和 return
这样的 shell 命令,这意味着它们 运行 in subshells,并且 运行ning exit
或者return
(或 break
或 ...)在子 shell 中对父 shell 没有这种影响(即 运行 宁功能)。这意味着您无法在管道内执行任何操作来直接创建函数 return。
在这种情况下,我认为你最好的选择是:
grep ... | otherstuff
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
return 1
fi
我有这个功能:
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
个问题:
正如我在评论中所说,您需要使 OPTIND
和 opt
成为函数的局部变量,因此它不会继承函数先前 运行 的值.要理解这是为什么,让我从您的原始功能开始(从您问题的第一个版本开始),并以 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
.
不过,在继续之前,我应该稍微解释一下 OPTIND
。 getopts
被设计为 运行 重复遍历标志(又名选项)参数,而 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
哎呀,现在都乱七八糟了,因为它继承了之前运行的OPTIND
和opt
。 OPTIND
为 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 +"{}" ""
第三,也是最重要的,子流程中管道的元素 运行。对于像 exit
和 return
这样的 shell 命令,这意味着它们 运行 in subshells,并且 运行ning exit
或者return
(或 break
或 ...)在子 shell 中对父 shell 没有这种影响(即 运行 宁功能)。这意味着您无法在管道内执行任何操作来直接创建函数 return。
在这种情况下,我认为你最好的选择是:
grep ... | otherstuff
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
return 1
fi