从全局匹配中返回随机项

从全局匹配中返回随机项

随着全局限定符可以zsh通过多种方式对文件名通配模式匹配的结果进行排序。例如,该模式*(om)将匹配当前目录中的所有非隐藏名称,并按修改时间戳排序。

然而,我有时想有一种方法随机化排序(例如,获取文件的随机样本)。据我所知,没有任何限定符可以直接执行此操作。

问题:如何从zsh文件名通配模式中获取随机的路径名列表?

答案1

使用随机排序键 (全局限定符 oe)::

*(Noe\''REPLY=$RANDOM,$RANDOM'\')

解释:

  • oe后面跟着一个单字符分隔符、一段代码和另一个分隔符。代码块可能不包含分隔符。特殊字符需要转义,以便在解析全局限定符本身时不会解析它们。
  • 我使用'作为分隔符(使用反斜杠,因为它需要转义),并用 包裹代码以'保护可能存在的特殊字符。这样我就可以编写任意代码,只要它不包含'.
  • 依次针对每个匹配的文件名执行该代码块。
  • REPLY最初设置为文件名,并且代码设置的任何内容都REPLY用作排序键)。

要随机采样$n元素,请添加[…]限定符:

*(Noe\''REPLY=$RANDOM,$RANDOM'\'[1,$n])

有时,某些元素会获得相同的排序键,因此所有排列的可能性并不相同,稍微倾向于保留按目录顺序将排序函数应用于列表的任何结果,但偏差很小。我使用它$RANDOM,$RANDOM作为排序键而不是$RANDOM为了减少偏差:$RANDOM是一个 15 位数字,当文件数量接近 2^15 时偏差会很明显。

请注意,$RANDOM如果不担心轻微的偏差,那么这对于采样来说已经足够了。它不适合任何涉及安全的事情。如果您想要安全的随机排列,请使用 GNU coreutilsshuf。 (如果您最喜欢的操作系统缺少本机shuf并且出于某种原因您不想安装 GNU coreutils,您可以尝试伊原的重新实现反而。)

securely_permuted=("${(0)$(printf '%s\0' *(N) | shuf -z))}")

或更简单的版本可能会遇到命令行长度限制:

securely_permuted=("${(0)$(shuf -z -- *(N)))}")

1实验上,排序是稳定的(例如,*(omoe\''REPLY=1'\')相当于*(om),但来自 的顺序*(oe\''REPLY=1'\')与 不匹配*(oN)。无论如何,它是有利于某些特定顺序的小偏差。

相关内容