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

285

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?

Ruggiero Spearman
fonte
4
Duplicado de stackoverflow.com/questions/886237/…
Pausado até novo aviso.
Sim, existem outras respostas legais nessa pergunta original também.
Ruggiero Spearman
então, você estava fazendo uma lista de palavras wpa? (apenas um palpite aleatório)
thahgr

Respostas:

360

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

Como jleedev apontou: sort -Rtambé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 .]

Joey
fonte
31
shufe sort -Rdiferem um pouco, porque sort -Rordena aleatoriamente os elementos de acordo com o hash deles, ou seja, sort -Rreunirá os elementos repetidos, enquanto shufembaralha todos os elementos aleatoriamente.
SeMeKh 28/08/12
146
Para usuários do OS X brew install coreutilsgshuf ...
:,
15
sort -Re shufdeve 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.
EfForEffort
18
Isso não é correto. "sort -R" usa uma chave de hash aleatória diferente toda vez que você a invoca, portanto produz uma saída diferente a cada vez.
Mark Pettit
3
Nota sobre aleatoriedade: de acordo com a documentação do GNU, "Por padrão, esses comandos usam um gerador pseudo-aleatório interno inicializado por uma pequena quantidade de entropia, mas podem ser direcionados para usar uma fonte externa com a opção --random-source = file."
Royce Williams
85

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

perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile
Moonyoung Kang
fonte
6
Alias ​​isso para embaralhar no OS X. Obrigado!
The Unfun Cat
Este foi o único script nesta página que retornou linhas aleatórias REAIS. Outras soluções awk geralmente imprimem saída duplicada.
Felipe Alvarez
1
Mas tenha cuidado porque no fora você vai perder uma linha :) Ela só vai se juntar com outra linha :)
JavaRunner
@JavaRunner: Presumo que você esteja falando de entrada sem deixar rasto \n; sim, isso \ndeve estar presente - e normalmente é - caso contrário, você obterá o que descreve.
mklement0
1
Maravilhosamente conciso. Sugiro substituir <STDIN>por <>, para que a solução funcione com a entrada de arquivos também.
mklement0
60

Esta resposta complementa as muitas ótimas respostas existentes das seguintes maneiras:

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

    • As funções recebem não apenas stdinentrada, mas também argumentos de nome de arquivo
    • As funções tomam medidas extras para lidar SIGPIPEda 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.


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 awkimplementação usada (por exemplo, a awkversão BSD usada no OSX é geralmente mais lenta que o GNU awke 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
  • Ruby 2.0.0
    • 0.289s
  • Perl 5.18.2
    • 0.589s
  • Pitão
    • 1.342scom Python 2.7.6; 2.407s(!) com Python 3.4.2
  • awk+ sort+cut
    • 3.003scom BSD awk; 2.388scom GNU awk(4.1.1); 1.811scom 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ça
  • Scala
    • 24.229s
  • bash loops + sort
    • 32.593s

Conclusões :

  • Use shuf, se puder - é o mais rápido de longe.
  • Ruby faz bem, seguido por Perl .
  • O Python é visivelmente mais lento que o 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 combo + awk+ compatível com POSIX como último recursosortcut ; qual awkimplementação você usa é importante ( mawké mais rápida que o GNU awk, BSDawk é mais lento).
  • Fique longe de sort -R, bashloops, 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):

  • Para o PowerShell (no Windows PowerShell, você precisará ajustar $OutputEncodingse 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-Randomcmdlet (embora o desempenho possa ser um problema); por exemplo:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)

  • Para 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))" %*
mklement0
fonte
Não SIGPIPE não existe no Windows, então eu usei este one-liner simples em vez disso: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]));"
Elig
@elig: Obrigado, mas omitir 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.
usar o seguinte comando
27

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'.

NickZoic
fonte
bom, RHEL 5.6 não tem shuf (
Maxim Egorushkin 12/11/11
1
Bem feito; Sugiro substituir <STDIN>por <>para que a solução funcione com a entrada de arquivos também.
Mklement0
20

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
Ruggiero Spearman
fonte
8
UUOC. passe o arquivo para o próprio awk.
precisa saber é o seguinte
1
Certo, eu depuro com head myfile | awk .... Então eu apenas mudo para gato; por isso foi deixado lá.
Ruggiero Spearman
Não é necessário -k1 -nclassificar, 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. -k1pode 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.
bonsaiviking
@ ghostdog74: A maioria dos chamados usos inúteis do gato é realmente útil por ser consistente entre os comandos canalizados e não. É melhor manter o cat filename |(ou < filename |) do que lembrar como cada programa recebe a entrada do arquivo (ou não).
ShreevatsaR
2
shuf () {awk 'BEGIN {srand ()} {print rand () "\ t" $ 0}' "$ @" | classificar | cut -f2-;}
Miau
16

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
ghostdog74
fonte
Bem feito, mas na prática muito mais lento que a resposta do OP , que combina awkcom sorte 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 awkimplementação usada). Uma ligeira simplificação seria substituir as linhas while (1){e if (e==d) {break}por while (e<d).
Mklement0
11

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).

scai
fonte
2
a "desvantagem" não é específica para o Python. Períodos finitos de PRNG podem ser contornados, realimentando o PRNG com a entropia do sistema, como /dev/urandomfaz. Para utilizá-lo a partir de Python: random.SystemRandom().shuffle(L).
jfs
o join () não precisa estar em '\ n' para que as linhas sejam impressas cada uma por si?
elegível
@elig: Não, porque .readLines()retorna as linhas com uma nova linha à direita.
usar o seguinte comando
9

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 sortnão entende números. Ele pode ser classificado por ordem lexicográfica (também conhecida como comparação normal de cadeias).

Michał Šrajer
fonte
Boa idéia para empacotar a resposta do próprio OP como uma função; se você anexar "$@", 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 awkformato 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.
mklement0
1
@ mklement0 Não notei a resposta do OP ao escrever o meu. rand () é multiplicado por 10e6 para fazê-lo funcionar com a classificação solaris ou hpux, tanto quanto me lembro. Boa idéia com "$ @"
Michał Šrajer
1
Entendi, obrigado; talvez você possa adicionar essa lógica da multiplicação à própria resposta; geralmente, de acordo com o POSIX, sortdeve ser capaz de lidar com frações decimais (mesmo com milhares de separadores, como acabei de notar).
mklement0
7

Ruby FTW:

ls | ruby -e 'puts STDIN.readlines.shuffle'
hoffmanc
fonte
1
Coisas boas; Se você usar puts ARGF.readlines.shuffle, poderá fazê-lo funcionar com os argumentos stdin input e filename.
mklement0
Ainda mais curto ruby -e 'puts $<.sort_by{rand}'- o ARGF já é um enumerável, para que possamos embaralhar as linhas classificando-as por valores aleatórios.
akuhn
6

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
dfrankow
fonte
6

Uma maneira simples e intuitiva seria usar shuf .

Exemplo:

Assuma words.txtcomo:

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
kmario23
fonte
4

Temos um pacote para fazer o mesmo 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 embaralhar, basta usar

rl 1000.txt
navigaid
fonte
3

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 -Re shufnão estão disponíveis, você pode usar o alias em seu bash_profile como:

alias shuf='python rand.py'
Jeff Wu
fonte
3

Se, como eu, você veio aqui para procurar uma alternativa shufpara o macOS, use randomize-lines.

Instale o randomize-linespacote (homebrew), que possui um rlcomando 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
Ahmad Awais
fonte
1
A instalação do Coreutils brew install coreutilsfornece o shufbinário como gshuf.
shadowtalker
2

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)'
swartzrock
fonte
Sedutoramente simples, mas, a menos que a Java VM deva ser iniciada de qualquer maneira, esse custo de inicialização é considerável; também não funciona bem com contagens de linhas grandes.
mklement0
1

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
}
Miau
fonte
Solução bacana legal que se assemelha à awksolução assistida pelo OP , mas o desempenho será um problema com maior entrada; o uso de um único $RANDOMvalor 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 shufdemora e 10 a 15 vezes enquanto a awksolução própria do OP demorar. Se você pode confiar em sortestar presente, awkdeve estar lá também.
mklement0
0

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.

Ayfan
fonte
Não funciona para arquivos grandes. I deram após 2 horas para um arquivo de 1 milhão + linhas
Stefan Haberl
0

Ainda não mencionado:

  1. O unsortutil. 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 ...]
  2. msort pode embaralhar por linha, mas geralmente é um exagero:

    seq 10 | msort -jq -b -l -n 1 -c r
agc
fonte
0

Outra awkvariante:

#!/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];
  }
}
Biziclop
fonte