Essa pode não ser a maneira mais eficiente, mas funciona:
shuf <file> > tmp
head -n $m tmp > out1
tail -n +$(( m + 1 )) tmp > out2
Com $m
contendo o número de linhas.
Eu tenho uma lista de dados, como
12345
23456
67891
-20000
200
600
20
...
Suponha que o tamanho desse conjunto de dados (ou seja, linhas de arquivo) seja N
. Eu quero desenhar aleatoriamente m
linhas deste arquivo de dados. Portanto, a saída deve ser dois arquivos, um é o arquivo que inclui essas m
linhas de dados e o outro inclui N-m
linhas de dados.
Existe uma maneira de fazer isso usando um comando do Linux?
Este script bash / awk escolhe linhas aleatoriamente e mantém a sequência original em ambos os arquivos de saída.
awk -v m=4 -v N=$(wc -l <file) -v out1=/tmp/out1 -v out2=/tmp/out2 \
'BEGIN{ srand()
do{ lnb = 1 + int(rand()*N)
if ( !(lnb in R) ) {
R[lnb] = 1
ct++ }
} while (ct<m)
} { if (R[NR]==1) print > out1
else print > out2
}' file
cat /tmp/out1
echo ========
cat /tmp/out2
Saída, com base nos dados da pergunta.
12345
23456
200
600
========
67891
-20000
20
Assim como todas as coisas do Unix, há um utilitário para essa TM .
Programa do dia: split
e split
dividirá um arquivo de muitas maneiras diferentes, -b
bytes, -l
linhas, -n
número de arquivos de saída. Nós estaremos usando a opção -l
. Como você deseja escolher linhas aleatórias e não apenas o primeiro m
,% ar_de% o arquivo será aleatoriamente primeiro. Se você quiser ler sobre sort
, consulte minha resposta aqui .
Agora, o código atual. É bem simples, na verdade:
sort -R input_file | split -l $m output_prefix
Isso criará dois arquivos, um com sort
linhas e um com m
linhas, denominado N-m
e output_prefixaa
.
Certifique-se de que output_prefixab
seja o arquivo maior que você deseja ou você obterá vários arquivos de comprimento m
(e um com m
).
Se você quiser garantir o tamanho correto, aqui está um pequeno código para fazer isso:
m=10 # size you want one file to be
N=$(wc -l input_file)
m=$(( m > N/2 ? m : N - m ))
sort -R input_file | split -l $m output_prefix
Editar: chegou ao meu conhecimento que algumas implementações de N % m
não têm um sinalizador sort
. Se você tiver -R
, poderá substituir perl
.
Se você não se importa em reordenar as linhas e você tem o GNU coreutils (ou seja, no Linux não-embarcado ou no Cygwin, não muito antigo, pois shuf
apareceu na versão 6.0), shuf
(" shuffle ") reordena as linhas de um arquivo aleatoriamente. Assim, você pode embaralhar o arquivo e distribuir as primeiras m linhas em um arquivo e o restante em outro.
Não há uma maneira ideal de fazer isso. Você não pode apenas encadear head
e tail
porque head
seria armazenado em buffer. Você pode usar split
, mas não tem flexibilidade com relação aos nomes dos arquivos de saída. Você pode usar awk
, claro:
<input shuf | awk -v m=$m '{ if (NR <= m) {print >"output1"} else {print} }'
Você pode usar sed
, que é obscuro, mas possivelmente mais rápido para arquivos grandes.
<input shuf | sed -e "1,${m} w output1" -e "1,${m} d" >output2
Ou você pode usar tee
para duplicar os dados, se sua plataforma tiver /dev/fd
; tudo bem se m for pequeno:
<input shuf | { tee /dev/fd/3 | head -n $m >output1; } 3>&1 | tail -n +$(($m+1)) >output2
Portável, você pode usar o awk para enviar cada linha por vez. Note que o awk não é muito bom em inicializar seu gerador de números aleatórios; a aleatoriedade não é definitivamente não adequada para criptografia, mas nem mesmo é muito boa para simulações numéricas. A semente será a mesma para todas as invocações de awk em qualquer sistema dentro de um período de um segundo.
<input awk -v N=$(wc -l <input) -v m=3 '
BEGIN {srand()}
{
if (rand() * N < m) {--m; print >"output1"} else {print >"output2"}
--N;
}'
Se você precisa de melhor aleatoriedade, você pode fazer a mesma coisa em Perl, que semeia o RNG decentemente.
<input perl -e '
open OUT1, ">", "output1" or die $!;
open OUT2, ">", "output2" or die $!;
my $N = 'wc -l <input';
my $m = $ARGV[0];
while (<STDIN>) {
if (rand($N) < $m) { --$m; print OUT1 $_; } else { print OUT2 $_; }
--$N;
}
close OUT1 or die $!;
close OUT2 or die $!;
' 42
Presumindo m = 7
e N = 21
:
cp ints ints.bak
for i in {1..7}
do
rnd=$((RANDOM%(21-i)+1))
# echo $rnd;
sed -n "${rnd}{p,q}" 10k.dat >> mlines
sed -i "${rnd}d" ints
done
Nota:
Se você substituir 7
por uma variável como $1
ou $m
, será necessário usar seq
, não a {from..to}
-notation, que não faz expansão variável.
Funciona excluindo linha por linha do arquivo, que fica cada vez mais curto, de modo que o número da linha, que pode ser removido, tenha que ficar menor e menor.
Tags text-processing linux shell