我记得曾在某处看到一个bash脚本使用case并shift遍历位置参数列表,当遇到参数时解析标志和带有参数的选项,并在解析后删除它们以只留下裸参数,稍后由其余部分处理脚本。
例如,在解析 的命令行时cp -R file1 -t /mybackup file2 -f,首先遍历参数,识别用户请求下降到目录 by -R,指定目标 by-t /mybackup并强制复制 by -f,然后将这些从参数列表中删除,留下file1 file2作为剩余参数处理的程序。
但我似乎无法记住/找出我看到的任何脚本。我只是希望能够做到这一点。我一直在谷歌搜索各种网站,并附上我检查过的相关页面列表。
本网站上的一个问题专门询问了“与顺序无关的选项”,但单一答案和被重复的问题的答案都没有考虑上述选项与正常参数混合的情况,我认为这是该人特别提及与订单无关的选项的原因。
由于bash的内置getopts似乎在第一个非选项参数处停止,因此它似乎不足以作为解决方案。这就是 Wooledge BashFAQ 的页面(见下文)解释如何重新排列参数的原因。但我想避免创建多个数组,以防参数列表很长。
由于shift不支持从参数列表的中间弹出单个参数,我不确定实现我所要求的直接方法是什么。
我想听听是否有人有任何解决方案来从参数列表的中间删除参数而不创建一个全新的数组。
我已经看过的页面:
Sté*_*las 10
POSIXly,选项的解析应该--在第一个非选项(或非选项参数)参数处或处停止,以先到者为准。所以在
cp -R file1 -t /mybackup file2 -f
Run Code Online (Sandbox Code Playgroud)
那是在file1,所以cp应该递归复制所有的file1,-t,/mybackup和file2进入-f目录。
GNUgetopt(3)无论如何(GNUcp用于分析选项(在这里你正在使用的GNUcp因为你正在使用的GNU特定-t选项)),除非$POSIXLY_CORRECT环境变量设置,接受参数后的选择。所以它实际上相当于 POSIX 选项样式解析的:
cp -R -t /mybackup -f -- file1 file2
Run Code Online (Sandbox Code Playgroud)
getopts内置的shell,即使在 GNU shell ( bash) 中也只处理 POSIX 样式。它也不支持长选项或带有可选参数的选项。
如果您想以与 GNU 相同的方式解析选项cp,则需要使用 GNU getopt(3)API。为此,如果在 Linux 上,您可以使用增强的getopt实用程序util-linux(该getopt命令的增强版本也已移植到其他一些 Unices,如FreeBSD)。
这getopt将以规范的方式重新排列选项,允许您简单地使用while/case循环解析它。
$ getopt -n "$0" -o t:Rf -- -Rf file1 -t/mybackup file2
-R -f -t '/mybackup' -- 'file1' 'file2'
Run Code Online (Sandbox Code Playgroud)
您通常将其用作:
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 $*"
Run Code Online (Sandbox Code Playgroud)
另请注意,这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",
[...]
Run Code Online (Sandbox Code Playgroud)
然后你可以使用getopt:
getopt -n cp -o abdfHilLnprst:uvxPRS:T -l archive... -- "$@"
Run Code Online (Sandbox Code Playgroud)
请记住,GNUcp的支持选项列表可能会从一个版本更改为下一个版本,并且getopt无法检查您是否向该--sparse选项传递了合法值。
因此,每次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
Run Code Online (Sandbox Code Playgroud)
运行时它设置为每个未处理的$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"
Run Code Online (Sandbox Code Playgroud)
"${2}" "${5}"
file1
file2
1
1
1
/mybackup
Run Code Online (Sandbox Code Playgroud)
这适用于bash, dash, zsh, ksh93, mksh... 好吧,我当时就放弃了尝试。在每个 shell 中,它还包含$[Rtf]flag和$targ。关键是所有getopts不想处理的参数的数字都保留了。
更改选项样式也没有什么区别。它的工作方式类似于-Rf -t/mybackup或-R -f -t /mybackup。它可以在列表的中间、列表的末尾或列表的开头工作......
尽管如此,最好的方法还是在你的参数列表中添加一个--for end of options,然后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
Run Code Online (Sandbox Code Playgroud)