Como posso embaralhar as linhas de um arquivo de texto na linha de comando do Unix ou em um script de shell?

Eu 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?

Você pode usar shuf . Em alguns sistemas, pelo menos (não parece estar em POSIX).

Como jleedev apontou: o sort -R também poderia ser uma opção. Em alguns sistemas, pelo menos; bem, você consegue a foto. Tem sido apontado que o sort -R realmente não embaralha, mas ordena os itens de acordo com o valor hash deles.

[Nota do editor: sort -R quase embaralha, exceto que as linhas duplicadas / chaves de sorting sempre acabam próximas umas das outras . Em outras palavras: somente com linhas / chaves de input únicas é um verdadeiro shuffle. Embora seja verdade que a ordem de saída é determinada por valores hash , a aleatoriedade vem da escolha de uma function hash aleatória – consulte o manual .]

Perl one-liner seria uma versão simples da solução da Maxim

 perl -MList::Util=shuffle -e 'print shuffle();' < myfile 

Essa resposta complementa as muitas grandes respostas existentes das seguintes maneiras:

  • As respostas existentes são empacotadas em funções de shell flexíveis :

    • As funções levam não apenas input stdin , mas também, alternativamente, argumentos de nome de arquivo
    • As funções tomam medidas extras para lidar com o SIGPIPE da maneira usual (terminação silenciosa com código de saída 141 ), ao invés de quebrar ruidosamente. Isso é importante ao canalizar a saída da function para um canal que é fechado antecipadamente, como durante a canalização para a head .
  • Uma comparação de desempenho é feita.


  • Função compatível com POSIX baseada em awk , sort e cut , adaptada da própria resposta do OP :
 shuf() { awk 'BEGIN {srand(); OFMT="%.17f"} {print rand(), $0}' "$@" | sort -k1,1n | cut -d ' ' -f2-; } 
  • Função baseada em Perl – adaptada da resposta de Moonyoung Kang :
 shuf() { perl -MList::Util=shuffle -e 'print shuffle(<>);' "$@"; } 
  • Função baseada em Python , adaptada da resposta da scai :
 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)) ' "$@"; } 
  • Função baseada em ruby , adaptada da resposta de hoffmanc :
 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 timings variem com o SO utilizado, as especificações da máquina, a implementação do awk usada (por exemplo, a versão do BSD awk usada no OSX é geralmente mais lenta que o GNU awk e especialmente o mawk ), isso deve fornecer uma sensação geral de desempenho relativo .

O arquivo de input é um arquivo de 1 milhão de linhas produzido com seq -f 'line %.0f' 1000000 .
Os tempos estão listados em ordem crescente (mais rápido primeiro):

  • shuf
    • 0.090s
  • Ruby 2.0.0
    • 0.289s
  • Perl 5.18.2
    • 0.589s
  • Python
    • 1.342s com Python 2.7.6; 2.407s (!) Com o Python 3.4.2
  • awk + sort + cut
    • 3.003s com BSD awk ; 2.388s com o GNU awk (4.1.1); 1.811s com mawk (1.3.4);

Para comparação adicional, as soluções não empacotadas como funções acima:

  • sort -R (não é um verdadeiro shuffle se houver linhas de input duplicadas)
    • 10.661s – alocar mais memory não parece fazer diferença
  • Scala
    • 24.229s
  • loops bash + sort
    • 32.593s

Conclusões

  • Use shuf , se você puder – é o mais rápido de longe.
  • Ruby faz bem, seguido por Perl .
  • Python é visivelmente mais lento que Ruby e Perl, e, comparando as versões do Python, o 2.7.6 é um pouco mais rápido que o 3.4.1
  • Use o awk + sort + cut combo compatível com POSIX como último recurso ; qual implementação awk você usa importa (o mawk é mais rápido que o GNU awk , o mawk do BSD é mais lento).
  • Fique longe do sort -R , bash loops e Scala.

Eu uso um pequeno script perl, que eu chamo de “unsort”:

 #!/usr/bin/perl use List::Util 'shuffle'; @list = ; print shuffle(@list); 

Eu também tenho uma versão delimitada por NULL, chamada “unsort0” … útil para uso com find -print0 e assim por diante.

PS: Votou-se ‘shuf’ também, eu não fazia ideia de que estava lá nos coreutils ultimamente … o acima pode ainda ser útil se o seu sistema não tiver ‘shuf’.

Aqui está uma primeira tentativa que é fácil no codificador, mas difícil na CPU, que preenche um número random para cada linha, classifica-os e, em seguida, retira o número random 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 

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 

saída

 $ cat file 1 2 3 4 5 6 7 8 9 10 $ ./shell.sh 7 5 10 9 6 8 2 1 3 4 

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 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 random.shuffle() do python. Não funcionará bem com muitos elementos (mais de 2080).

A function simples baseada no 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 praticamente qualquer UNIX. Testado no Linux, Solaris e HP-UX.

Atualizar:

Observe que a multiplicação de zeros à esquerda ( %06d ) e rand() faz com que funcione corretamente também em sistemas nos quais a sort não entende números. Ele pode ser classificado por ordem lexicográfica (também conhecido como comparação normal de string).

Ruby FTW:

 ls | ruby -e 'puts STDIN.readlines.shuffle' 

Um liner para Python baseado na resposta de scai , mas a) pega stdin, b) torna o resultado repetível com seed, c) seleciona 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 

Este é um script python que eu 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, portanto, você pode colocar alias em seu bash_profile como:

 alias shuf='python rand.py' 

Nós temos um pacote para fazer o trabalho:

 sudo apt-get install randomize-lines 

Exemplo:

Crie uma lista ordenada de números e salve-a em 1000.txt:

 seq 1000 > 1000.txt 

para embaralhá-lo, basta usar

 rl 1000.txt 

Se você gosta de mim, veio aqui procurar uma alternativa para shuf para macOS e depois usar randomize-lines .

Instale o pacote randomize-lines (homebrew), que possui um comando rl que possui funcionalidade semelhante ao 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 

Uma maneira simples e intuitiva seria usar shuf .

Exemplo:

Assuma o words.txt como:

 the an linux ubuntu life good breeze 

Para embaralhar as linhas, faça:

 $ shuf words.txt 

o qual lançaria as linhas embaralhadas para a saída padrão ; Então, você tem que canalizá- lo para um arquivo de saída como:

 $ shuf words.txt > shuffled_words.txt 

Uma dessas execuções de shuffle poderia render:

 breeze the linux an ubuntu good life 

Se você tem o Scala instalado, aqui está um one-liner para embaralhar a input:

 ls -1 | scala -e 'for (l <- util.Random.shuffle(io.Source.stdin.getLines.toList)) println(l)' 

Esta function bash tem a dependência mínima (somente sort e bash):

 shuf() { while read -rx;do echo $RANDOM$'\x1f'$x done | sort | while IFS=$'\x1f' read -rxy;do echo $y done } 

No Windows Você pode tentar este arquivo de lote para ajudá-lo a embaralhar seus dados.txt, O uso do código de lote é

 C:\> type list.txt | shuffle.bat > maclist_temp.txt 

Depois de emitir este comando, maclist_temp.txt irá conter uma lista aleatória de linhas.

Espero que isto ajude.

Não mencionado até o momento:

  1. O util unsort . Sintaxe (um pouco orientada a listas 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 ...] 
  2. msort pode baralhar por linha, mas geralmente é um exagero:

     seq 10 | msort -jq -b -l -n 1 -cr 

Outra variante do awk :

 #!/usr/bin/awk -f # usage: # awk -f randomize_lines.awk lines.txt # usage after "chmod +x randomize_lines.awk": # randomize_lines.awk lines.txt BEGIN { FS = "\n"; srand(); } { lines[ rand()] = $0; } END { for( k in lines ){ print lines[k]; } }