ImH*_*ere 10 shell-script posix
我知道可以"$@"使用数组进行反转:
arr=( "$@" )
Run Code Online (Sandbox Code Playgroud)
并使用此答案,反转数组。
但这需要一个具有数组的外壳。
也可以使用tac:
set -- $( printf '%s\n' "$@" | tac )
Run Code Online (Sandbox Code Playgroud)
但是,断裂,如果参数有空格,制表符或换行符(假设的缺省值$IFS),或者包含通配符(除非通配符被禁用预先)并移除空元素,并需要GNUtac命令(使用tail -r是GNU系统的稍微更便携式外但某些实现在大输入时失败)。
有没有一种方法可以在不使用数组的情况下可移植地反转 shell 位置参数,即使参数包含空格、换行符或通配符或可能为空也能正常工作?
ImH*_*ere 14
可移植地,不需要数组(只有位置参数)并且可以使用空格和换行符:
flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done
Run Code Online (Sandbox Code Playgroud)
例子:
$ set -- one "two 22" "three
> 333" four
$ printf '<%s>' "$@"; echo
<one><two 22><three
333><four>
$ flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done
$ printf '<%s>' "$@"; echo
<four><three
333><two 22><one>
Run Code Online (Sandbox Code Playgroud)
的值flag控制 的扩展${flag-"$@"}。当flag被设置时,它膨胀到的值flag(即使它是空的)。所以,当flag是flag='',${flag....}扩展到一个空值,并得到由壳去除,因为它是不带引号的。当 未flag设置时, 的值${flag-"$@"}将扩展为 右侧的值-,即 的扩展"$@",因此它成为所有位置参数(引用,不会删除任何空值)。此外,该变量flag最终被擦除(未设置)而不影响以下代码。
Kus*_*nda 10
当不想使用任何数组作为临时存储时,我们可以使用这样一个事实,即for循环始终迭代一组不变的静态元素。从某种意义上说,我们可以将循环本身用作位置参数的临时存储,同时以相反的顺序重建列表。
为了能够做到这一点,我们还需要在第一次迭代时清空列表。下面的代码使用一个简单的标志来检测是否必须这样做。当列表被清空时,标志被切换。
flag=true
for value do
if "$flag"; then
set --
flag=false
fi
set -- "$value" "$@"
done
Run Code Online (Sandbox Code Playgroud)
不幸的是,这很慢,因为位置参数列表在每次迭代中都有效地重建(set -- some-list设置所有位置参数)。所述bash壳大约需要50秒至逆转1和10000之间的整数,而zsh只需在15秒内。
使用Isaac 的技巧with ${flag-"$@"}(扩展为"$@"仅当flag未设置时)实际上会使整个事情运行得更慢;1 分 50 秒 (!)bash和 25 秒zsh。
我猜想这是由于一些实施特殊性在壳上如何进行测试$flag和/或扩大"$@"的${flag-"$@"}扩张(外壳有可能会扩大"$@"内部两次?)。
如果允许我们使用数组作为临时存储(这不是标准的,但仍然相当可移植,因为我们经常知道我们正在为哪个 shell 编写脚本),我们可以使用值$#(位置参数的数量)作为在循环位置参数时存储当前值的索引。shift在每次迭代中使用减少此值会产生从数组末尾向开头插入值的效果。
在 中bash,数组从索引 0 开始,并且由于在shift赋值之后出现,最后一个位置参数将存储在索引 1 而不是 0。这对代码的工作方式没有影响bash,它仍然会生成正确的结果,但是它使它也可以工作zsh(默认情况下使用基于 1 的数组索引)。
代码:
tmp=()
for value do
tmp[$#]=$value
shift
done
set -- "${tmp[@]}"
Run Code Online (Sandbox Code Playgroud)
使用bash或zsh,这使用大约 0.6 秒来反转 1 到 10000 之间的整数。
Sté*_*las 10
从我的这个答案复制到Bash - 使用 glob 打印反转文件列表,以反转位置参数列表 POSIXly:
eval "set -- $(awk 'BEGIN {for (i = ARGV[1]; i; i--) printf " \"${"i"}\""}' "$#")"
Run Code Online (Sandbox Code Playgroud)
或者在几行上稍微更清晰:
eval "set -- $(
awk '
BEGIN {
for (i = ARGV[1]; i; i--)
printf " \"${" i "}\""
}' "$#"
)"
Run Code Online (Sandbox Code Playgroud)
这个想法是用来awk帮助生成set -- "${3}" "${2}" "${1}"shell 代码eval来解释例如何时"$@"有 3 个元素。
对于大型列表,它可能比使用 shell 循环快得多,尤其是在每次迭代时重建列表的循环。该awk代码可以由壳循环,提供同样的输出(如@mosvy注释中显示)所取代,但在我与bash5 + gawk4.1的测试中,它仍然是两倍,除了非常短名单慢。
在 中zsh,您将使用Oa显式设计用于反转数组的参数标志:
set -- "${(Oa)@}"
Run Code Online (Sandbox Code Playgroud)
在我的系统上(比@Kusalananda 稍慢),在set $(seq 10000)使用 bash5 + gawk4.2.1获得的位置参数列表上,该eval方法需要0.4 秒,而@Kusalananda需要 1 分钟,@Isaac需要 2 分钟(zsh的Oa方法需要约 2 毫秒)。
与sh和awk从busybox的1.30.1,那些定时成为:分别0.06S,11S,11S。
| 归档时间: |
|
| 查看次数: |
2036 次 |
| 最近记录: |