这里有一个小 bash 函数供您使用。正如您所说,它抓取“一批”行,在文件中具有随机起点。
randline() {
local lines c r _
# cache the number of lines in this file in a symlink in the temp dir
lines="/tmp/${1//\//-}.lines"
if [ -h "$lines" ] && [ "$lines" -nt "${1}" ]; then
c=$(ls -l "$lines" | sed 's/.* //')
else
read c _ < <(wc -l $1)
ln -sfn "$c" "$lines"
fi
# Pick a random number...
r=$[ $c * ($RANDOM * 32768 + $RANDOM) / (32768 * 32768) ]
echo "start=$r" >&2
# And start displaying $2 lines before that number.
head -n $r "$1" | tail -n ${2:-1}
}
根据需要编辑echo 行。
此解决方案的优点是管道更少、资源密集型管道更少(即没有| sort ... |)、更少的平台依赖性(即没有sort -R,这是GNU-sort-specific)。
请注意,这依赖于 Bash 的 $RANDOM 变量,它实际上可能是随机的,也可能不是随机的。此外,如果您的源文件包含超过 32768^2 行,它将错过行,并且如果您指定的行数 (N) 大于 1 并且随机开始,则会出现失败边缘情况point 距离开始小于 N 行。解决这个问题留给读者作为练习。 :)
更新 #1:
mklement0 在 cmets 中就head ... | tail ... 方法的潜在性能问题提出了一个很好的问题。老实说,我不知道答案,但我希望head 和tail 都经过充分优化,不会在显示输出之前缓冲所有输入。
如果我的希望没有实现,这里有一个替代方案。这是一个基于 awk 的“滑动窗口”尾巴。我会将它嵌入到我之前编写的函数中,以便您可以根据需要对其进行测试。
randline() {
local lines c r _
# Line count cache, per the first version of this function...
lines="/tmp/${1//\//-}.lines"
if [ -h "$lines" ] && [ "$lines" -nt "${1}" ]; then
c=$(ls -l "$lines" | sed 's/.* //')
else
read c _ < <(wc -l $1)
ln -sfn "$c" "$lines"
fi
r=$[ $c * ($RANDOM * 32768 + $RANDOM) / (32768 * 32768) ]
echo "start=$r" >&2
# This simply pipes the functionality of the `head | tail` combo above
# through a single invocation of awk.
# It should handle any size of input file with the same load/impact.
awk -v lines=${2:-1} -v count=0 -v start=$r '
NR < start { next; }
{ out[NR]=$0; count++; }
count > lines { delete out[start++]; count--; }
END {
for(i=start;i<start+lines;i++) {
print out[i];
}
}
' "$1"
}
嵌入的 awk 脚本替换了函数之前版本中的head ... | tail ... 管道。它的工作原理如下:
- 它会跳过行,直到由早期随机化确定的“开始”。
- 它将当前行记录到一个数组中。
- 如果数组大于我们要保留的行数,则会删除第一条记录。
- 在文件末尾打印记录的数据。
结果是 awk 进程不应该增加它的内存占用,因为输出数组被修整的速度与构建的速度一样快。
注意:我还没有用你的数据实际测试过。
更新 #2:
Hrm,随着对您的问题的更新,您想要 N 随机行而不是从随机点开始的一行块,我们需要不同的策略。您施加的系统限制非常严格。以下可能是一个选项,也使用 awk,随机数仍然来自 Bash:
randlines() {
local lines c r _
# Line count cache...
lines="/tmp/${1//\//-}.lines"
if [ -h "$lines" ] && [ "$lines" -nt "${1}" ]; then
c=$(ls -l "$lines" | sed 's/.* //')
else
read c _ < <(wc -l $1)
ln -sfn "$c" "$lines"
fi
# Create a LIST of random numbers, from 1 to the size of the file ($c)
for (( i=0; i<$2; i++ )); do
echo $[ $c * ($RANDOM * 32768 + $RANDOM) / (32768 * 32768) + 1 ]
done | awk '
# And here inside awk, build an array of those random numbers, and
NR==FNR { lines[$1]; next; }
# display lines from the input file that match the numbers.
FNR in lines
' - "$1"
}
这是通过将随机行号列表作为“第一个”文件输入 awk 来实现的,然后让 awk 打印“第二个”文件中的行号包含在“第一个”文件中的行。它使用wc 来确定要生成的随机数的上限。这意味着您将阅读此文件两次。如果您有文件中行数的其他来源(例如数据库),请在此处插入。 :)
一个限制因素可能是第一个文件的大小,它必须加载到内存中。我相信 30000 个随机数应该只占用大约 170KB 的内存,但是数组在 RAM 中的表示方式取决于您使用的 awk 的实现。 (虽然通常,awk 实现(包括 Ubuntu 中的 Gawk)非常擅长将内存浪费降至最低。)
这对你有用吗?