我记得在某个地方看到过一个bash
脚本,使用case
和shift
遍历位置参数列表,在遇到参数时解析带有参数的标志和选项,并在解析后删除它们以仅留下裸露的参数,这些参数稍后由其余的参数处理脚本。
例如,在解析 的命令行时cp -R file1 -t /mybackup file2 -f
,它会首先遍历参数,识别出用户已请求下降到目录-R
,指定目标-t /mybackup
并强制复制-f
,然后从参数列表中删除这些参数,留下file1 file2
作为剩余参数处理的程序。
但我似乎无法记住/找出我每次看到的任何脚本。我只是希望能够做到这一点。我一直在谷歌搜索各个网站并附加我检查过的相关页面的列表。
该网站上的一个问题专门询问了“与顺序无关的选项”,但单个答案和它所复制的问题的答案都没有考虑像上面这样的情况,其中选项与普通参数混合,我认为这是此人特别提及的原因与顺序无关选项。
由于bash
的内置函数getopts
似乎在第一个非选项参数处停止,因此它似乎不足以作为解决方案。这就是为什么 Wooledge BashFAQ 页面(见下文)解释了如何重新排列参数。但我想避免创建多个数组,以防参数列表很长。
由于shift
不支持从参数列表中间弹出单个参数,因此我不确定什么是实现我所要求的内容的直接方法。
我想听听是否有人有任何解决方案可以从参数列表中间删除参数而不创建全新的数组。
我已经看过的页面:
- http://mywiki.wooledge.org/ComplexOptionParsing#Rearranging_arguments
- http://mywiki.wooledge.org/BashFAQ/035
- 在 bash shell 脚本中使用 getopts 获取长命令行选项和短命令行选项
- http://wiki.bash-hackers.org/scripting/posparams
- http://wiki.bash-hackers.org/howto/getopts_tutorial
- $@ 中 args 的 bash 参数大小写
- 在 bash 脚本中实现顺序独立选项的规范方法是什么?
- 如何处理 shell 脚本中的开关?
答案1
POSIXly,选项的解析应该在--
第一个非选项(或非选项参数)参数处停止,以先到者为准。所以在
cp -R file1 -t /mybackup file2 -f
那是在file1
,所以cp
应该递归地将所有file1
、-t
、/mybackup
和复制file2
到-f
目录中。
然而, GNU getopt(3)
(GNUcp
用于解析选项(这里您使用的是 GNU,cp
因为您使用的是 GNU 特定的-t
选项)),除非$POSIXLY_CORRECT
设置了环境变量,否则接受参数后的选项。所以它实际上相当于 POSIX 选项样式解析:
cp -R -t /mybackup -f -- file1 file2
getopts
内置的 shell,即使在 GNU shell ( ) 中也只bash
处理 POSIX 风格。它也不支持长选项或带有可选参数的选项。
如果您想像 GNU 一样解析选项cp
,则需要使用 GNU getopt(3)
API。为此,如果在 Linux 上,您可以使用getopt
(util-linux
该getopt
命令的增强版本也被移植到其他一些 Unices,例如自由BSD)。
这getopt
将以规范的方式重新排列选项,使您可以通过循环简单地解析它while/case
。
$ getopt -n "$0" -o t:Rf -- -Rf file1 -t/mybackup file2
-R -f -t '/mybackup' -- 'file1' 'file2'
您通常将其用作:
parsed_options=$(
getopt -n "$0" -o t:Rf -- "$@"
) || exit
eval "set -- $parsed_options"
while [ "$#" -gt 0 ]; do
case $1 in
(-[Rf]) shift;;
(-t) shift 2;;
(--) shift; break;;
(*) exit 1 # should never be reached.
esac
done
echo "Now, the arguments are $*"
另请注意,它将getopt
以与 GNU 相同的方式解析选项cp
。特别是,它支持长选项(并以缩写形式输入它们)并尊重$POSIXLY_CORRECT
环境变量(设置时会禁用对参数后选项的支持),就像 GNU 一样cp
。
请注意,使用 gdb 并打印接收到的参数getopt_long()
可以帮助构建参数getopt(1)
:
(gdb) bt
#0 getopt_long (argc=2, argv=0x7fffffffdae8, options=0x4171a6 "abdfHilLnprst:uvxPRS:T", long_options=0x417c20, opt_index=0x0) at getopt1.c:64
(gdb) set print pretty on
(gdb) p *long_options@40
$10 = {{
name = 0x4171fb "archive",
has_arg = 0,
flag = 0x0,
val = 97
}, {
name = 0x417203 "attributes-only",
[...]
然后你可以使用getopt
如下:
getopt -n cp -o abdfHilLnprst:uvxPRS:T -l archive... -- "$@"
请记住,GNUcp
支持的选项列表可能会从一个版本更改为下一个版本,并且getopt
无法检查您是否向该--sparse
选项传递了合法值。
答案2
因此,每次getopts
处理参数时,它不会期望将 shell 变量设置$OPTIND
为参数列表中应处理的下一个数字,并返回 0 以外的值。如果$OPTIND
设置为值 1,getopts
则 POSIX 指定接受新的参数列表。因此,这只是监视getopts
返回,为任何失败的返回保存增量计数器加号$OPTIND
,移走失败的参数,并重置$OPTIND
每个失败的尝试。您可以像这样使用它opts "$@"
- 尽管您想要自定义case
循环或将其保存到变量中并将该部分更改为eval $case
.
opts() while getopts :Rt:f opt || {
until command shift "$OPTIND" || return 0
args=$args' "${'"$((a=${a:-0}+$OPTIND))"'}"'
[ -n "${1#"${1#-}"}" ]
do OPTIND=1; done 2>/dev/null; continue
}; do case "$opt" in
R) Rflag=1 ;;
t) tflag=1 ;
targ=$OPTARG ;;
f) fflag=1 ;;
esac; done
运行时它设置$args
为每个getopts
未处理的参数...所以...
set -- -R file1 -t /mybackup file2 -f
args= opts "$@"; eval "set -- $args"
printf %s\\n "$args"
printf %s\\n "$@"
printf %s\\n "$Rflag" "$tflag" "$fflag" "$targ"
输出
"${2}" "${5}"
file1
file2
1
1
1
/mybackup
这适用于bash
, dash
, zsh
, ksh93
, mksh
... 好吧,我当时就放弃了尝试。在每个 shell 中,它还包含$[Rtf]flag
和$targ
。关键是所有getopts
不想处理的参数的数字都保留了。
更改选项样式也没有什么区别。它的工作方式类似于-Rf -t/mybackup
或-R -f -t /mybackup
。它可以在列表的中间、列表的末尾或列表的开头工作......
尽管如此,最好的办法只是在你的参数列表上粘贴一个--
for 选项结尾,然后shift "$(($OPTIND-1))"
在处理运行结束时执行getopts
。这样你就可以删除所有已处理的参数和保留参数列表的尾部。
我喜欢做的一件事是将长期权翻译成短期权——我以非常相似的方式做到这一点,这就是为什么这个答案很容易得到——前我跑getopts
。
i=0
until [ "$((i=$i+1))" -gt "$#" ]
do case "$1" in
--Recursive) set -- "$@" "-R" ;;
--file) set -- "$@" "-f" ;;
--target) set -- "$@" "-t" ;;
*) set -- "$@" "$1" ;;
esac; shift; done