Quero embaralhar as linhas de um arquivo de texto aleatoriamente e criar um novo arquivo. O arquivo pode ter vários milhares de linhas.
Como posso fazer isso com cat
, awk
, cut
, etc?
Quero embaralhar as linhas de um arquivo de texto aleatoriamente e criar um novo arquivo. O arquivo pode ter vários milhares de linhas.
Como posso fazer isso com cat
, awk
, cut
, etc?
Respostas:
Você pode usar shuf
. Em alguns sistemas, pelo menos (não parece estar no POSIX).
Como jleedev apontou: sort -R
também pode ser uma opção. Em alguns sistemas, pelo menos; bem, você entendeu. Foi apontado que sort -R
, na verdade, não se embaralha, mas ordena os itens de acordo com seu valor de hash.
[Nota do editor: sort -R
quase embaralha, exceto que linhas duplicadas / teclas de classificação sempre terminam próximas uma da outra . Em outras palavras: somente com linhas / teclas de entrada exclusivas, é uma verdadeira reprodução aleatória. Embora seja verdade que a ordem de saída é determinada por valores de hash , a aleatoriedade vem da escolha de uma função de hash aleatória - consulte o manual .]
shuf
e sort -R
diferem um pouco, porque sort -R
ordena aleatoriamente os elementos de acordo com o hash deles, ou seja, sort -R
reunirá os elementos repetidos, enquanto shuf
embaralha todos os elementos aleatoriamente.
brew install coreutils
gshuf ...
sort -R
e shuf
deve ser visto como completamente diferente. sort -R
é determinístico. Se você ligar duas vezes em horários diferentes na mesma entrada, obterá a mesma resposta. shuf
, por outro lado, produz saída aleatória; portanto, provavelmente fornecerá saída diferente na mesma entrada.
O one-liner do Perl seria uma versão simples da solução da Maxim
perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile
\n
; sim, isso \n
deve estar presente - e normalmente é - caso contrário, você obterá o que descreve.
<STDIN>
por <>
, para que a solução funcione com a entrada de arquivos também.
Esta resposta complementa as muitas ótimas respostas existentes das seguintes maneiras:
As respostas existentes são empacotadas em funções de shell flexíveis :
stdin
entrada, mas também argumentos de nome de arquivoSIGPIPE
da maneira usual (terminação silenciosa com código de saída 141
), em vez de quebrar ruidosamente. Isso é importante ao canalizar a saída da função para um tubo que é fechado mais cedo, como ao canalizar head
.Uma comparação de desempenho é feita.
awk
, sort
ecut
adaptada da própria resposta do OP :shuf() { awk 'BEGIN {srand(); OFMT="%.17f"} {print rand(), $0}' "$@" |
sort -k1,1n | cut -d ' ' -f2-; }
shuf() { perl -MList::Util=shuffle -e 'print shuffle(<>);' "$@"; }
shuf() { python -c '
import sys, random, fileinput; from signal import signal, SIGPIPE, SIG_DFL;
signal(SIGPIPE, SIG_DFL); lines=[line for line in fileinput.input()];
random.shuffle(lines); sys.stdout.write("".join(lines))
' "$@"; }
Consulte a seção inferior para uma versão do Windows desta função.
shuf() { ruby -e 'Signal.trap("SIGPIPE", "SYSTEM_DEFAULT");
puts ARGF.readlines.shuffle' "$@"; }
Comparação de desempenho:
Nota: Esses números foram obtidos em um iMac de final de 2012 com Intel Core i5 de 3,2 GHz e um Fusion Drive, executando o OSX 10.10.3. Embora os tempos variem com o SO usado, as especificações da máquina, a awk
implementação usada (por exemplo, a awk
versão BSD usada no OSX é geralmente mais lenta que o GNU awk
e especialmente mawk
), isso deve fornecer uma sensação geral de desempenho relativo .
Arquivo de entrada é um arquivo de 1 milhão de linhas produzido com seq -f 'line %.0f' 1000000
.
Os horários são listados em ordem crescente (mais rápido primeiro):
shuf
0.090s
0.289s
0.589s
1.342s
com Python 2.7.6; 2.407s
(!) com Python 3.4.2awk
+ sort
+cut
3.003s
com BSD awk
; 2.388s
com GNU awk
(4.1.1); 1.811s
com mawk
(1.3.4);Para uma comparação mais detalhada, as soluções não empacotadas como funções acima:
sort -R
(não é uma verdadeira reprodução aleatória se houver linhas de entrada duplicadas)
10.661s
- alocar mais memória não parece fazer diferença24.229s
bash
loops + sort
32.593s
Conclusões :
shuf
, se puder - é o mais rápido de longe.awk
+ compatível com POSIX como último recursosort
cut
; qual awk
implementação você usa é importante ( mawk
é mais rápida que o GNU awk
, BSDawk
é mais lento).sort -R
, bash
loops, e Scala.Versões do Windows da solução Python (o código Python é idêntico, exceto pelas variações nas citações e pela remoção das instruções relacionadas ao sinal, que não são suportadas no Windows):
$OutputEncoding
se desejar enviar caracteres não ASCII por meio do pipeline):# Call as `shuf someFile.txt` or `Get-Content someFile.txt | shuf`
function shuf {
$Input | python -c @'
import sys, random, fileinput;
lines=[line for line in fileinput.input()];
random.shuffle(lines); sys.stdout.write(''.join(lines))
'@ $args
}
Observe que o PowerShell pode embaralhar nativamente por meio de seu Get-Random
cmdlet (embora o desempenho possa ser um problema); por exemplo:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)
cmd.exe
(um arquivo em lotes):Salve no arquivo shuf.cmd
, por exemplo:
@echo off
python -c "import sys, random, fileinput; lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write(''.join(lines))" %*
python -c "import sys, random; lines = [x for x in sys.stdin.read().splitlines()] ; random.shuffle(lines); print(\"\n\".join([line for line in lines]));"
from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);
a solução original é suficiente e mantém a flexibilidade de também ser capaz de passar argumentos de nome de arquivo - não há necessidade de alterar mais nada (exceto citações) - consulte a nova seção que adicionei no inferior.
Eu uso um pequeno script perl, que eu chamo de "unsort":
#!/usr/bin/perl
use List::Util 'shuffle';
@list = <STDIN>;
print shuffle(@list);
Eu também tenho uma versão delimitada por NULL, chamada "unsort0" ... útil para usar com o find -print0 e assim por diante.
PS: Votado como 'shuf' também, eu não fazia ideia de que existia no coreutils hoje em dia ... o acima pode ainda ser útil se o seu sistema não tiver 'shuf'.
<STDIN>
por <>
para que a solução funcione com a entrada de arquivos também.
Aqui está uma primeira tentativa que é fácil no codificador, mas difícil na CPU, que acrescenta um número aleatório a cada linha, classifica-os e retira o número aleatório de cada linha. Com efeito, as linhas são classificadas aleatoriamente:
cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled
head myfile | awk ...
. Então eu apenas mudo para gato; por isso foi deixado lá.
-k1 -n
classificar, pois a saída do awk's rand()
é um decimal entre 0 e 1 e porque tudo o que importa é que ele é reordenado de alguma forma. -k1
pode ajudar a acelerar, ignorando o restante da linha, embora a saída de rand () deva ser exclusiva o suficiente para causar um curto-circuito na comparação.
cat filename |
(ou < filename |
) do que lembrar como cada programa recebe a entrada do arquivo (ou não).
aqui está um script awk
awk 'BEGIN{srand() }
{ lines[++d]=$0 }
END{
while (1){
if (e==d) {break}
RANDOM = int(1 + rand() * d)
if ( RANDOM in lines ){
print lines[RANDOM]
delete lines[RANDOM]
++e
}
}
}' file
resultado
$ cat file
1
2
3
4
5
6
7
8
9
10
$ ./shell.sh
7
5
10
9
6
8
2
1
3
4
awk
com sort
e cut
. Por não mais do que vários milhares de linhas, isso não faz muita diferença, mas com contagens mais altas isso importa (o limite depende da awk
implementação usada). Uma ligeira simplificação seria substituir as linhas while (1){
e if (e==d) {break}
por while (e<d)
.
Um one-liner para python:
python -c "import random, sys; lines = open(sys.argv[1]).readlines(); random.shuffle(lines); print ''.join(lines)," myFile
E para imprimir apenas uma única linha aleatória:
python -c "import random, sys; print random.choice(open(sys.argv[1]).readlines())," myFile
Mas veja este post para as desvantagens do python random.shuffle()
. Não funcionará bem com muitos elementos (mais de 2080).
/dev/urandom
faz. Para utilizá-lo a partir de Python: random.SystemRandom().shuffle(L)
.
.readLines()
retorna as linhas com uma nova linha à direita.
Função simples baseada em awk fará o trabalho:
shuffle() {
awk 'BEGIN{srand();} {printf "%06d %s\n", rand()*1000000, $0;}' | sort -n | cut -c8-
}
uso:
any_command | shuffle
Isso deve funcionar em quase qualquer UNIX. Testado em Linux, Solaris e HP-UX.
Atualizar:
Observe que zeros à esquerda ( %06d
) e rand()
multiplicação fazem com que funcione corretamente também em sistemas onde sort
não entende números. Ele pode ser classificado por ordem lexicográfica (também conhecida como comparação normal de cadeias).
"$@"
, também funcionará com os arquivos como entrada. Não há motivo para multiplicar rand()
, porque sort -n
é capaz de ordenar frações decimais. É, no entanto, uma boa idéia para controle de awk
formato de saída 's, porque com o formato padrão, %.6g
, rand()
saída vontade o número ocasional nas exponencial notação. Embora a prática de embaralhar até 1 milhão de linhas seja discutível o suficiente na prática, é fácil oferecer suporte a mais linhas sem pagar muita penalidade de desempenho; por exemplo %.17f
.
sort
deve ser capaz de lidar com frações decimais (mesmo com milhares de separadores, como acabei de notar).
Ruby FTW:
ls | ruby -e 'puts STDIN.readlines.shuffle'
puts ARGF.readlines.shuffle
, poderá fazê-lo funcionar com os argumentos stdin input e filename.
ruby -e 'puts $<.sort_by{rand}'
- o ARGF já é um enumerável, para que possamos embaralhar as linhas classificando-as por valores aleatórios.
Um liner para Python com base na resposta do scai , mas a) pega stdin, b) torna o resultado repetível com a semente, c) escolhe apenas 200 de todas as linhas.
$ cat file | python -c "import random, sys;
random.seed(100); print ''.join(random.sample(sys.stdin.readlines(), 200))," \
> 200lines.txt
Uma maneira simples e intuitiva seria usar shuf
.
Exemplo:
Assuma words.txt
como:
the
an
linux
ubuntu
life
good
breeze
Para embaralhar as linhas, faça:
$ shuf words.txt
o que jogaria as linhas embaralhadas para a saída padrão ; Então, você deve canalizá- lo para um arquivo de saída como:
$ shuf words.txt > shuffled_words.txt
Uma dessas execuções aleatórias poderia render:
breeze
the
linux
an
ubuntu
good
life
Este é um script python que salvei como rand.py na minha pasta pessoal:
#!/bin/python
import sys
import random
if __name__ == '__main__':
with open(sys.argv[1], 'r') as f:
flist = f.readlines()
random.shuffle(flist)
for line in flist:
print line.strip()
No Mac OSX sort -R
e shuf
não estão disponíveis, você pode usar o alias em seu bash_profile como:
alias shuf='python rand.py'
Se, como eu, você veio aqui para procurar uma alternativa shuf
para o macOS, use randomize-lines
.
Instale o randomize-lines
pacote (homebrew), que possui um rl
comando com funcionalidade semelhante a shuf
.
brew install randomize-lines
Usage: rl [OPTION]... [FILE]...
Randomize the lines of a file (or stdin).
-c, --count=N select N lines from the file
-r, --reselect lines may be selected multiple times
-o, --output=FILE
send output to file
-d, --delimiter=DELIM
specify line delimiter (one character)
-0, --null set line delimiter to null character
(useful with find -print0)
-n, --line-number
print line number with output lines
-q, --quiet, --silent
do not output any errors or warnings
-h, --help display this help and exit
-V, --version output version information and exit
brew install coreutils
fornece o shuf
binário como gshuf
.
Se você tem o Scala instalado, aqui está uma lista para embaralhar a entrada:
ls -1 | scala -e 'for (l <- util.Random.shuffle(io.Source.stdin.getLines.toList)) println(l)'
Esta função bash tem a dependência mínima (somente classificação e bash):
shuf() {
while read -r x;do
echo $RANDOM$'\x1f'$x
done | sort |
while IFS=$'\x1f' read -r x y;do
echo $y
done
}
awk
solução assistida pelo OP , mas o desempenho será um problema com maior entrada; o uso de um único $RANDOM
valor embaralha corretamente apenas até 32.768 linhas de entrada; Embora você possa estender esse intervalo, provavelmente não vale a pena: por exemplo, na minha máquina, executar seu script em 32.768 linhas curtas de entrada leva cerca de 1 segundo, o que equivale a 150 vezes o tempo que a execução shuf
demora e 10 a 15 vezes enquanto a awk
solução própria do OP demorar. Se você pode confiar em sort
estar presente, awk
deve estar lá também.
No Windows, você pode tentar este arquivo em lotes para ajudá-lo a embaralhar seus dados.txt. O uso do código em lotes é
C:\> type list.txt | shuffle.bat > maclist_temp.txt
Após emitir este comando, o maclist_temp.txt conterá uma lista aleatória de linhas.
Espero que isto ajude.
Ainda não mencionado:
O unsort
util. Sintaxe (um pouco orientada para a lista de reprodução):
unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic]
[--identity] [--filenames[=profile]] [--separator sep] [--concatenate]
[--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null]
[--linefeed] [file ...]
msort
pode embaralhar por linha, mas geralmente é um exagero:
seq 10 | msort -jq -b -l -n 1 -c r