从bash中的大文件中获取随机行

Ste*_*gin 5 bash command-line random-sample line-processing

如何n从无法放入内存的非常大的文件中获取随机行。

如果我可以在随机化之前或之后添加过滤器也很棒。


更新 1

就我而言,规格是:

  • > 1 亿行
  • > 10GB 文件
  • 通常随机批量大小 10000-30000
  • 512RAM 托管 ubuntu 服务器 14.10

所以从文件中丢失几行不会是一个大问题,因为无论如何它们都有 1 万分之一的机会,但性能和资源消耗将是一个问题

jm6*_*666 8

在这样的限制因素下,下面的方法会更好。

  • 寻找文件中的随机位置(例如,您将在某行“内部”)
  • 从这个位置向后走,找到给定行的开始
  • 继续打印整行

为此,您需要一个可以在文件中查找的工具,例如perl.

use strict;
use warnings;
use Symbol;
use Fcntl qw( :seek O_RDONLY ) ;
my $seekdiff = 256; #e.g. from "rand_position-256" up to rand_positon+256

my($want, $filename) = @ARGV;

my $fd = gensym ;
sysopen($fd, $filename, O_RDONLY ) || die("Can't open $filename: $!");
binmode $fd;
my $endpos = sysseek( $fd, 0, SEEK_END ) or die("Can't seek: $!");

my $buffer;
my $cnt;
while($want > $cnt++) {
    my $randpos = int(rand($endpos));   #random file position
    my $seekpos = $randpos - $seekdiff; #start read here ($seekdiff chars before)
    $seekpos = 0 if( $seekpos < 0 );

    sysseek($fd, $seekpos, SEEK_SET);   #seek to position
    my $in_count = sysread($fd, $buffer, $seekdiff<<1); #read 2*seekdiff characters

    my $rand_in_buff = ($randpos - $seekpos)-1; #the random positon in the buffer

    my $linestart = rindex($buffer, "\n", $rand_in_buff) + 1; #find the begining of the line in the buffer
    my $lineend = index $buffer, "\n", $linestart;            #find the end of line in the buffer
    my $the_line = substr $buffer, $linestart, $lineend < 0 ? 0 : $lineend-$linestart;

    print "$the_line\n";
}
Run Code Online (Sandbox Code Playgroud)

将上述内容保存到某个文件中,例如“randlines.pl”并将其用作:

perl randlines.pl wanted_count_of_lines file_name
Run Code Online (Sandbox Code Playgroud)

例如

perl randlines.pl 10000 ./BIGFILE
Run Code Online (Sandbox Code Playgroud)

该脚本执行非常低级的 IO 操作,即非常快。(在我的笔记本上,从 10M 中选择 30k 行需要半秒)。


Ste*_*gin 0

#!/bin/bash
#contents of bashScript.sh

file="$1";
lineCnt=$2;
filter="$3";
nfilter="$4";
echo "getting $lineCnt lines from $file matching '$filter' and not matching '$nfilter'" 1>&2;

totalLineCnt=$(cat "$file" | grep "$filter" | grep -v "$nfilter" | wc -l | grep -o '^[0-9]\+');
echo "filtered count : $totalLineCnt" 1>&2;

chances=$( echo "$lineCnt/$totalLineCnt" | bc -l );
echo "chances : $chances" 1>&2;

cat "$file" | awk 'BEGIN { srand() } rand() <= $chances { print; }' | grep "$filter" | grep -v "$nfilter" | head -"$lineCnt";
Run Code Online (Sandbox Code Playgroud)

用法:

获取 1000 个随机样本

bashScript.sh /path/to/largefile.txt 1000  
Run Code Online (Sandbox Code Playgroud)

行有数字

bashScript.sh /path/to/largefile.txt 1000 "[0-9]"
Run Code Online (Sandbox Code Playgroud)

没有迈克和简

bashScript.sh /path/to/largefile.txt 1000 "[0-9]" "mike|jane"
Run Code Online (Sandbox Code Playgroud)