在 Bash 脚本中,我想从输入文件中挑选出 N 个随机行并输出到另一个文件。
如何才能做到这一点?
sort -R
,因为它做了很多多余的工作,特别是对于长文件。您可以使用 $RANDOM
、% wc -l
、jot
、sed -n
(à la stackoverflow.com/a/6022431/563329) 和 bash 功能(数组、命令重定向等)来定义您自己的 peek
函数,该函数将在5,000,000 行文件。
随机排序文件并选择前 100
行:
lines=100
input_file=/usr/share/dict/words
# This is the basic selection method
<$input_file sort -R | head -n $lines
# If the file has duplicates that must never cause duplicate results
<$input_file sort | uniq | sort -R | head -n $lines
# If the file has blank lines that must be filtered, use sed
<$input_file sed $'/^[ \t]*$/d' | sort -R | head -n $lines
当然 <$input_file
可以替换为任何管道标准输入。这(sort -R
和 $'...\t...'
让 sed
匹配制表符)适用于 GNU/Linux 和 BSD/macOS。
sort
实际上将相同的行排序在一起,因此如果您可能有重复的行并且您安装了 shuf
(一个 gnu 工具),那么最好使用它。
shuf -n
的作用非常迅速。
sort -R
可能是 GNU 选项,安装 GNU coreutils。顺便说一句,shuf
也是 coreutils 的一部分。
sort -R input | head -n <num_lines>
。输入文件为 279GB,有 2bi+ 行。不过不能分享。无论如何,关键是您可以通过随机播放将 some 行保留在内存中,以随机选择要输出的内容。无论您需要什么,Sort 都会对 整个 文件进行排序。
好吧,根据对 shuf 答案的评论,他在一分钟内改组了 78 000 000 000 行。
已接受的挑战...
编辑:我打破了自己的记录
powershuf 在 0.047 秒内完成
$ time ./powershuf.py -n 10 --file lines_78000000000.txt > /dev/null
./powershuf.py -n 10 --file lines_78000000000.txt > /dev/null 0.02s user 0.01s system 80% cpu 0.047 total
之所以这么快,是因为我没有读取整个文件,只是将文件指针移动 10 次,然后打印指针后面的行。
老尝试
首先,我需要一个 78.000.000.000 行的文件:
seq 1 78 | xargs -n 1 -P 16 -I% seq 1 1000 | xargs -n 1 -P 16 -I% echo "" > lines_78000.txt
seq 1 1000 | xargs -n 1 -P 16 -I% cat lines_78000.txt > lines_78000000.txt
seq 1 1000 | xargs -n 1 -P 16 -I% cat lines_78000000.txt > lines_78000000000.txt
这给了我一个包含 780 亿换行符的文件 ;-)
现在对于 shuf 部分:
$ time shuf -n 10 lines_78000000000.txt
shuf -n 10 lines_78000000000.txt 2171.20s user 22.17s system 99% cpu 36:35.80 total
瓶颈是 CPU 并且没有使用多个线程,它将 1 个核心固定为 100%,其他 15 个未使用。
Python 是我经常使用的,所以我将使用它来加快速度:
#!/bin/python3
import random
f = open("lines_78000000000.txt", "rt")
count = 0
while 1:
buffer = f.read(65536)
if not buffer: break
count += buffer.count('\n')
for i in range(10):
f.readline(random.randint(1, count))
这让我不到一分钟:
$ time ./shuf.py
./shuf.py 42.57s user 16.19s system 98% cpu 59.752 total
我在配备 i9 和三星 NVMe 的 Lenovo X1 Extreme 2nd gen 上执行此操作,这为我提供了足够的读写速度。
我知道它可以变得更快,但我会留出一些空间让其他人尝试一下。
-r
选项,否则 shuf 不会两次输出同一行,当然这需要额外的处理时间。
我的首选选项非常快,我采样了一个制表符分隔的数据文件,它有 13 列、23.1M 行、2.0GB 未压缩。
# randomly sample select 5% of lines in file
# including header row, exclude blank lines, new seed
time \
awk 'BEGIN {srand()}
!/^$/ { if (rand() <= .05 || FNR==1) print > "data-sample.txt"}' data.txt
# awk tsv004 3.76s user 1.46s system 91% cpu 5.716 total
seq 1 100 | python3 -c 'print(__import__("random").choice(__import__("sys").stdin.readlines()))'
# Function to sample N lines randomly from a file
# Parameter $1: Name of the original file
# Parameter $2: N lines to be sampled
rand_line_sampler() {
N_t=$(awk '{print $1}' $1 | wc -l) # Number of total lines
N_t_m_d=$(( $N_t - $2 - 1 )) # Number oftotal lines minus desired number of lines
N_d_m_1=$(( $2 - 1)) # Number of desired lines minus 1
# vector to have the 0 (fail) with size of N_t_m_d
echo '0' > vector_0.temp
for i in $(seq 1 1 $N_t_m_d); do
echo "0" >> vector_0.temp
done
# vector to have the 1 (success) with size of desired number of lines
echo '1' > vector_1.temp
for i in $(seq 1 1 $N_d_m_1); do
echo "1" >> vector_1.temp
done
cat vector_1.temp vector_0.temp | shuf > rand_vector.temp
paste -d" " rand_vector.temp $1 |
awk '$1 != 0 {$1=""; print}' |
sed 's/^ *//' > sampled_file.txt # file with the sampled lines
rm vector_0.temp vector_1.temp rand_vector.temp
}
rand_line_sampler "parameter_1" "parameter_2"
下面的“c”是要从输入中选择的行数。根据需要修改:
#!/bin/sh
gawk '
BEGIN { srand(); c = 5 }
c/NR >= rand() { lines[x++ % c] = $0 }
END { for (i in lines) print lines[i] }
' "$@"
c
行。充其量你可以说被选中的平均行数是 c
。
c/NR
将保证大于从 rand
为 前 c 行生成的任何值。 之后,它可能大于也可能不大于 rand
。因此我们可以说lines
最后包含至少 c 个条目,并且通常比这更多,即不 正好是c 个条目。此外,文件的前 c 行总是被选取,因此整个选取并不是所谓的随机选取。
sort -R
更随机吗?