这是我目前的答案:
pop() {
local n=$(($1 - ${2:-1}))
if [ -n "$ZSH_VERSION" -o -n "$BASH_VERSION" ]; then
POP_EXPR='set -- "${@:1:'$n'}"'
elif [ $n -ge 500 ]; then
POP_EXPR="set -- $(seq -s " " 1 $n | sed 's/[0-9]\+/"${\0}"/g')"
else
local index=0
local arguments=""
while [ $index -lt $n ]; do
index=$((index+1))
arguments="$arguments \"\${$index}\""
done
POP_EXPR="set -- $arguments"
fi
}
请注意 local 不是 POSIX,但由于所有主要的 sh shell 都支持它(特别是我在问题中要求的那些)并且没有它会导致严重的错误,我决定将它包含在这个主导功能。但这里有一个完全兼容的 POSIX 版本,带有混淆参数以减少出现错误的机会:
pop() {
__pop_n=$(($1 - ${2:-1}))
if [ -n "$ZSH_VERSION" -o -n "$BASH_VERSION" ]; then
POP_EXPR='set -- "${@:1:'$__pop_n'}"'
elif [ $__pop_n -ge 500 ]; then
POP_EXPR="set -- $(seq -s " " 1 $__pop_n | sed 's/[0-9]\+/"${\0}"/g')"
else
__pop_index=0
__pop_arguments=""
while [ $__pop_index -lt $__pop_n ]; do
__pop_index=$((__pop_index+1))
__pop_arguments="$__pop_arguments \"\${$__pop_index}\""
done
POP_EXPR="set -- $__pop_arguments"
fi
}
用法
pop1() {
pop $#
eval "$POP_EXPR"
echo "$@"
}
pop2() {
pop $# 2
eval "$POP_EXPR"
echo "$@"
}
pop1 a b c #=> a b
pop1 $(seq 1 1000) #=> 1 .. 999
pop2 $(seq 1 1000) #=> 1 .. 998
pop_next
使用 pop 创建 POP_EXPR 变量后,您可以使用以下命令
更改它以省略更多参数的函数:
pop_next() {
if [ -n "$BASH_VERSION" -o -n "$ZSH_VERSION" ]; then
local np="${POP_EXPR##*:}"
np="${np%\}*}"
POP_EXPR="${POP_EXPR%:*}:$((np == 0 ? 0 : np - 1))}\""
return
fi
POP_EXPR="${POP_EXPR% \"*}"
}
pop_next 比 posix shell 中的 pop 简单得多(尽管它是
在 zsh 和 bash 上比 pop 稍微复杂)
它是这样使用的:
main() {
pop $#
pop_next
eval "$POP_EXPR"
}
main 1 2 3 #=> 1
POP_EXPR 和变量范围
请注意,如果您不打算在之后立即使用 eval "$POP_EXPR"
pop 和 pop_next,如果你不小心界定某些函数调用的范围
在操作之间可能会更改 POP_EXPR 变量和混乱的事情
向上。为避免这种情况,只需将local POP_EXPR 放在每个函数的开头即可
使用pop(如果可用)。
f() {
local POP_EXPR
pop $#
g 1 2
eval "$POP_EXPR"
printf '%s' "f=$*"
}
g() {
local POP_EXPR
pop $#
eval "$POP_EXPR"
printf '%s, ' "g=$*"
}
f a b c #=> g=1, f=a b
popgen.sh
这个特殊的功能对于我的目的来说已经足够了,但我确实创建了一个
脚本来生成进一步优化的函数。
https://gist.github.com/fcard/e26c5a1f7c8b0674c17c7554fb0cd35c#file-popgen-sh
这里不使用外部工具来提高性能的方法之一是
意识到有几个小字符串连接很慢,所以这样做
它们分批使功能大大加快。调用脚本
popgen.sh -gN1,N2,N3 创建一个处理操作的弹出函数
根据参数计数分批 N1、N2 或 N3。剧本也
包含其他技巧,示例和解释如下:
$ sh popgen \
> -g 10,100 \ # concatenate strings in batches\
> -w \ # overwrite current file\
> -x9 \ # hardcode the result of the first 9 argument counts\
> -t1000 \ # starting at argument count 1000, use external tools\
> -p posix \ # prefix to add to the function name (with a underscore)\
> -s '' \ # suffix to add to the function name (with a underscore)\
> -c \ # use the command popsh instead of seq/sed as the external tool\
> -@ \ # on zsh and bash, use the subarray method (checks on runtime)\
> -+ \ # use bash/zsh extensions (removes runtime check from -@)\
> -nl \ # don't use 'local'\
> -f \ # use 'function' syntax\
> -o pop.sh # output file
可以使用popgen.sh -t500 -g1 -@ 生成与上述函数等效的函数。
在包含 popgen.sh 的 gist 中,您将找到一个 popsh.c 文件,该文件可以
编译并用作默认 shell 的专用、更快的替代方案
外部工具,它将被使用生成的任何函数使用
popgen.sh -c ... 如果它可以被 shell 访问为 popsh。
或者,您可以创建任何名为 popsh 的函数或工具并使用
它在它的位置。
基准测试
基准函数:
我用于基准测试的脚本可以在这个要点上找到:
https://gist.github.com/fcard/f4aec7e567da2a8e97962d5d3f025ad4#file-popbench-sh
基准函数位于以下几行中:
https://gist.github.com/fcard/f4aec7e567da2a8e97962d5d3f025ad4#file-popbench-sh-L233-L301
脚本可以这样使用:
$ sh popbench.sh \
> -s dash \ # shell used by the benchmark, can be dash/bash/ash/zsh/ksh.\
> -f posix \ # function to be tested\
> -i 10000 \ # number of times that the function will be called per test\
> -a '\0' \ # replacement pattern to model arguments by index (uses sed)\
> -o /dev/stdout \ # where to print the results to (concatenates, defaults to stdout)\
> -n 5,10,1000 # argument sizes to test
它将输出带有real、user和sys时间值的time -p样式表,
以及一个int 值,用于内部,在基准内计算
使用date处理。
次
以下是int的调用结果
$ sh popbench.sh -s $shell -f $function -i 10000 -n 1,5,10,100,1000,10000
posix指第二和第三条,subarray指第一条,
而final 指的是整体。
value count 1 5 10 100 1000 10000
---------------------------------------------------------------------------------------
dash/final 0m0.109s 0m0.183s 0m0.275s 0m2.270s 0m16.122s 1m10.239s
ash/final 0m0.104s 0m0.175s 0m0.273s 0m2.337s 0m15.428s 1m11.673s
ksh/final 0m0.409s 0m0.557s 0m0.737s 0m3.558s 0m19.200s 1m40.264s
bash/final 0m0.343s 0m0.414s 0m0.470s 0m1.719s 0m17.508s 3m12.496s
---------------------------------------------------------------------------------------
bash/subarray 0m0.135s 0m0.179s 0m0.224s 0m1.357s 0m18.911s 3m18.007s
dash/posix 0m0.171s 0m0.290s 0m0.447s 0m3.610s 0m17.376s 1m8.852s
ash/posix 0m0.109s 0m0.192s 0m0.285s 0m2.457s 0m14.942s 1m10.062s
ksh/posix 0m0.416s 0m0.581s 0m0.768s 0m4.677s 0m18.790s 1m40.407s
bash/posix 0m0.409s 0m0.739s 0m1.145s 0m10.048s 0m58.449s 40m33.024s
在 zsh 上
对于较大的参数计数,使用 eval 设置 set -- ... 在 zsh no 上非常慢
不管是什么方法,保存为eval 'set -- "${@:1:$# - 1}"'。即使作为
简单的修改,将其更改为eval "set -- ${@:1:$# - 1}"
(忽略它不适用于带空格的参数)使其成为两个订单
速度慢很多。
value count 1 5 10 100 1000 10000
---------------------------------------------------------------------------------------
zsh/subarray 0m0.203s 0m0.227s 0m0.233s 0m0.461s 0m3.643s 0m38.396s
zsh/final 0m0.399s 0m0.416s 0m0.441s 0m0.722s 0m4.205s 0m37.217s
zsh/posix 0m0.718s 0m0.913s 0m1.182s 0m6.200s 0m46.516s 42m27.224s
zsh/eval-zsh 0m0.419s 0m0.353s 0m0.375s 0m0.853s 0m5.771s 32m59.576s
更多基准测试
有关更多基准测试,包括仅使用外部工具、c popsh 工具或朴素算法,请参阅此文件:
https://gist.github.com/fcard/f4aec7e567da2a8e97962d5d3f025ad4#file-benchmarks-md
它是这样生成的:
$ git clone https://gist.github.com/f4aec7e567da2a8e97962d5d3f025ad4.git popbench
$ cd popbench
$ sh popgen_run.sh
$ sh popbench_run.sh --fast # or without --fast if you have a day to spare
$ sh poptable.sh -g >benchmarks.md
结论
这是对该主题为期一周的研究的结果,我认为
我会分享它。希望它不会太长,我试着把它修剪成主要的
与要点链接的信息。这最初是为了回答
(Remove last argument from argument list of shell script (bash)) 但我觉得专注于 POSIX
跑题了。
此处链接的要点中的所有代码均在 MIT 许可下获得许可。