在Bash脚本中,我想要从输入文件中随机选择N行并输出到另一个文件。如何实现?
sort -R
更随机吗? - Mona Jalal随机排序文件并选择前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 工具),最好使用它来进行排序。 - Kevinsort -R input | head -n <num_lines>
。输入文件大小为279GB,有20亿+行。虽然不能分享它,但无论如何,重点是您可以使用shuffle将一些行保留在内存中,以进行随机选择要输出的内容。无论您的需求是什么,排序都将对整个文件进行排序。 - Rubens根据一条评论,Shuf回答在不到一分钟的时间内洗牌了780亿行。
接受挑战...
编辑:我打破了自己的记录
$ 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次并打印指针后的行。
首先,我需要一个拥有78000000000行的文件:
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亿个换行符的文件 ;-)
现在进行乱序部分:
$ 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二代来完成这项工作,读写速度非常快。
我知道速度可以更快,但我会留出一些空间让其他人试试。
行数计数器 来源:Luther Blissett
我的首选选项速度非常快,我对一个包含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()))'
仅为完整起见,并且因为它可以从Arch的社区存储库中获取:还有一个名为shuffle
的工具,但它没有任何命令行开关来限制行数,并在其手册中发出警告:“由于shuffle将输入读入内存,因此可能无法处理非常大的文件。”
# 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
。 - user1934428c/NR
将保证比 rand
产生的任何值都要大。然后,在这之后,c/NR
可能也可能不会比 rand
大。因此,我们可以说最终 lines
包含至少 c 个条目,并且一般情况下更多,即不正好 c 条目。此外,文件的前 c 行总是被选择的,因此整个选择并不能称为随机选择。 - user1934428
sort -R
,因为它会对长文件进行大量的冗余操作。你可以使用$RANDOM
、% wc -l
、jot
、sed -n
(参考 https://dev59.com/5G025IYBdhLWcg3wZ1Ny#6022431)以及 bash 功能(数组、命令重定向等)来定义自己的peek
函数,该函数可以在 500 万行的文件上运行。 - isomorphismes