Come posso mescolare le righe di un file di testo sulla riga di comando di Unix o in uno script di shell?


285

Voglio mescolare le righe di un file di testo in modo casuale e creare un nuovo file. Il file può contenere diverse migliaia di righe.

Come posso farlo con cat, awk, cut, ecc?



Sì, ci sono anche altre belle risposte in quella domanda originale.
Ruggiero Spearman,

quindi, stavi facendo una lista di parole WPA? (solo un'ipotesi casuale)
thahgr

Risposte:


361

È possibile utilizzare shuf. Almeno su alcuni sistemi (non sembra essere in POSIX).

Come ha sottolineato jleedev: sort -Rpotrebbe anche essere un'opzione. Almeno su alcuni sistemi; bene, ottieni l'immagine. È stato sottolineato che sort -Rnon si mescola realmente, ma invece ordina gli oggetti in base al loro valore di hash.

[Nota del redattore: sort -R quasi si mescolano, tranne per il fatto che le righe duplicate / i tasti di ordinamento finiscono sempre uno accanto all'altro . In altre parole: solo con righe / tasti di input univoci è un vero shuffle. Mentre è vero che l'ordine di output è determinato dai valori di hash , la casualità deriva dalla scelta di una funzione di hash casuale - consultare il manuale .]


31
shufe sort -Rdifferiscono leggermente, perché sort -Rordina casualmente gli elementi in base all'hash di loro, che è, sort -Rmetterà insieme gli elementi ripetuti, mentre shufmescola tutti gli elementi in modo casuale.
SeMeKh,

146
Per gli utenti di OS X brew install coreutilsgshuf ...
:,

15
sort -Re shufdovrebbe essere visto come completamente diverso. sort -Rè deterministico. Se lo chiami due volte in momenti diversi sullo stesso input otterrai la stessa risposta. shufd'altra parte, produce output randomizzati, quindi molto probabilmente darà output diversi sullo stesso input.
EfForEffort,

18
Questo non è corretto "sort -R" usa una chiave hash casuale diversa ogni volta che la invochi, quindi produce ogni volta output diverso.
Mark Pettit,

3
Nota sulla casualità: secondo i documenti GNU, "Per impostazione predefinita questi comandi utilizzano un generatore pseudo-casuale interno inizializzato da una piccola quantità di entropia, ma possono essere indirizzati a utilizzare una fonte esterna con l'opzione --random-source = file".
Royce Williams,

86

Perl one-liner sarebbe una versione semplice della soluzione di Maxim

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

6
Ho modificato questa procedura in modo da riprodurre in ordine casuale su OS X. Grazie!
The Unfun Cat,

Questo è stato l'unico script in questa pagina che ha restituito righe casuali REALI. Altre soluzioni awk spesso stampavano copie duplicate.
Felipe Alvarez,

1
Ma fai attenzione perché in fuori
perderai

@JavaRunner: suppongo che tu stia parlando di input senza trascinamento \n; sì, \ndeve essere presente - e in genere lo è - altrimenti otterrai ciò che descrivi.
mklement0,

1
Meravigliosamente conciso. Suggerisco di sostituirlo <STDIN>con <>, quindi la soluzione funziona anche con l'input dei file .
mklement0,

60

Questa risposta completa le molte grandi risposte esistenti nei seguenti modi:

  • Le risposte esistenti sono raggruppate in funzioni shell flessibili :

    • Le funzioni accettano non solo stdininput, ma in alternativa anche argomenti di nome file
    • Le funzioni adottano misure aggiuntive per gestirle SIGPIPEnel solito modo (terminazione silenziosa con codice di uscita 141), anziché interrompere in modo rumoroso. Ciò è importante quando si esegue il piping dell'uscita della funzione su un tubo che viene chiuso in anticipo, ad esempio quando si esegue il piping a head.
  • Viene effettuato un confronto delle prestazioni .


  • Funzione conforme a POSIX basata su awk, sortecut adattata dalla risposta dell'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))
' "$@"; }

Vedere la sezione inferiore per una versione Windows di questa funzione.

shuf() { ruby -e 'Signal.trap("SIGPIPE", "SYSTEM_DEFAULT");
                     puts ARGF.readlines.shuffle' "$@"; }

Confronto delle prestazioni:

Nota: questi numeri sono stati ottenuti su un iMac di fine 2012 con Intel Core i5 a 3,2 GHz e Fusion Drive, con OSX 10.10.3. Mentre i tempi variano a seconda del sistema operativo utilizzato, delle specifiche della macchina, awkdell'implementazione utilizzata (ad esempio, la awkversione BSD utilizzata su OSX è generalmente più lenta di GNU awke soprattutto mawk), ciò dovrebbe fornire un senso generale delle prestazioni relative .

Il file di input è un file di 1 milione di righe prodotto con seq -f 'line %.0f' 1000000.
I tempi sono elencati in ordine crescente (prima il più veloce):

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

Per un ulteriore confronto, le soluzioni non impacchettate come funzioni sopra:

  • sort -R (non un vero shuffle se ci sono righe di input duplicate)
    • 10.661s - l'allocazione di più memoria non sembra fare la differenza
  • Scala
    • 24.229s
  • bash loop + sort
    • 32.593s

Conclusioni :

  • Usa shuf, se puoi , è di gran lunga il più veloce.
  • Ruby fa bene, seguito da Perl .
  • Python è notevolmente più lento di Ruby e Perl e, confrontando le versioni di Python, 2.7.6 è un po 'più veloce di 3.4.1
  • Utilizzare la combinazione + awk+ conforme a POSIX come ultima risorsasortcut ; quale awkimplementazione usi è importante ( mawkè più veloce di GNU awk, BSD awkè più lenta).
  • Stare lontano da sort -R, bashloop e Scala.

Versioni di Windows della soluzione Python (il codice Python è identico, ad eccezione delle variazioni di quotazione e rimozione delle istruzioni relative al segnale, che non sono supportate su Windows):

  • Per PowerShell (in Windows PowerShell, dovrai modificare $OutputEncodingse desideri inviare caratteri non ASCII tramite la 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  
}

Si noti che PowerShell può eseguire il shuffle nativo tramite il suo Get-Randomcmdlet (sebbene le prestazioni possano essere un problema); per esempio:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)

  • Per cmd.exe(un file batch):

Salva su file shuf.cmd, ad esempio:

@echo off
python -c "import sys, random, fileinput; lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write(''.join(lines))" %*

SIGPIPE non esiste su Windows, quindi ho usato questo semplice one-liner: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]));"
eleggo il

@elig: Grazie, ma omettere from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);dalla soluzione originale è sufficiente e mantiene la flessibilità di poter anche passare gli argomenti del nome file - non c'è bisogno di cambiare nient'altro (tranne che per la citazione) - si prega di consultare la nuova sezione che ho aggiunto al parte inferiore.
mklement0

27

Uso un piccolo script perl, che io chiamo "unsort":

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

Ho anche una versione delimitata da NULL, chiamata "unsort0" ... utile per l'uso con find -print0 e così via.

PS: Anche io ho votato "shuf", non avevo idea che ci fosse in coreutils in questi giorni ... quanto sopra potrebbe ancora essere utile se i tuoi sistemi non hanno "shuf".


bello, RHEL 5.6 non ha shuf (
Maxim

1
Ben fatto; Suggerisco di sostituirlo <STDIN>con <>per far funzionare la soluzione anche con l'input dai file .
mklement0,

20

Ecco un primo tentativo facile sul programmatore ma difficile sulla CPU che antepone un numero casuale a ciascuna riga, li ordina e quindi elimina il numero casuale da ciascuna riga. In effetti, le linee sono ordinate casualmente:

cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled

8
UUOC. passa il file a awk stesso.
ghostdog74,

1
Bene, debug con head myfile | awk .... Quindi lo cambio semplicemente in gatto; ecco perché è stato lasciato lì.
Ruggiero Spearman,

Non è necessario -k1 -nordinare, poiché l'output di awk rand()è un decimale tra 0 e 1 e perché tutto ciò che conta è che in qualche modo viene riordinato. -k1potrebbe aiutare ad accelerarlo ignorando il resto della linea, sebbene l'output di rand () dovrebbe essere abbastanza unico da cortocircuitare il confronto.
bonsaiviking

@ ghostdog74: La maggior parte dei cosiddetti usi inutili di cat sono in realtà utili per essere coerenti tra i comandi in pipe e non. Meglio mantenere il cat filename |(o < filename |) che ricordare come ogni singolo programma accetta (o meno) l'input di file.
ShreevatsaR,

2
shuf () {awk 'BEGIN {srand ()} {print rand () "\ t" $ 0}' "$ @" | ordina | cut -f2-;}
Meow,

16

ecco una sceneggiatura fantastica

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

produzione

$ cat file
1
2
3
4
5
6
7
8
9
10

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

Ben fatto, ma in pratica molto più lento della risposta dell'OP , che si combina awkcon sorte cut. Per non più di diverse migliaia di righe non fa molta differenza, ma con un numero di righe maggiore è importante (la soglia dipende awkdall'implementazione utilizzata). Una leggera semplificazione sarebbe quella di sostituire le linee while (1){e if (e==d) {break}con while (e<d).
mklement0

11

Un one-liner per Python:

python -c "import random, sys; lines = open(sys.argv[1]).readlines(); random.shuffle(lines); print ''.join(lines)," myFile

E per stampare una sola riga casuale:

python -c "import random, sys; print random.choice(open(sys.argv[1]).readlines())," myFile

Ma vedi questo post per gli svantaggi di Python random.shuffle(). Non funzionerà bene con molti (più di 2080) elementi.


2
lo "svantaggio" non è specifico di Python. I periodi di PRNG finiti potrebbero essere aggirati rimettendo il PRNG con entropia dal sistema come /dev/urandomfa. Per utilizzarlo da Python: random.SystemRandom().shuffle(L).
jfs,

non è necessario che join () sia su '\ n' in modo che le righe vengano stampate ognuna a modo suo?
partecipazione

@elig: No, perché .readLines()restituisce le righe con una nuova riga finale.
mklement0

9

La semplice funzione basata su awk farà il lavoro:

shuffle() { 
    awk 'BEGIN{srand();} {printf "%06d %s\n", rand()*1000000, $0;}' | sort -n | cut -c8-
}

utilizzo:

any_command | shuffle

Questo dovrebbe funzionare su quasi tutti gli UNIX. Testato su Linux, Solaris e HP-UX.

Aggiornare:

Si noti che i primi zeri ( %06d) e la rand()moltiplicazione lo fanno funzionare correttamente anche su sistemi in cui i sortnumeri non sono compresi. Può essere ordinato in base all'ordine lessicografico (noto anche come confronto di stringhe normali).


Buona idea per impacchettare la risposta dell'OP come funzione; se lo aggiungi "$@", funzionerà anche con i file come input. Non c'è motivo di moltiplicarsi rand(), perché sort -nè in grado di ordinare le frazioni decimali. È comunque una buona idea controllare awkil formato di output, perché con il formato predefinito %.6g, rand()produrrà il numero occasionale in notazione esponenziale . Mentre mescolare fino a 1 milione di linee è probabilmente abbastanza pratico, è facile supportare più linee senza pagare gran parte della penalità prestazionale; es %.17f.
mklement0

1
@ mklement0 Non ho notato la risposta degli OP mentre scrivevo la mia. rand () viene moltiplicato per 10e6 per farlo funzionare con solaris o hpux sort per quanto mi ricordo. Buona idea con "$ @"
Michał Šrajer

1
Capito grazie; forse potresti aggiungere questa logica per la moltiplicazione alla risposta stessa; in generale, secondo POSIX, sortdovrebbe essere in grado di gestire le frazioni decimali (anche con migliaia di separatori, come ho appena notato).
mklement0

7

Ruby FTW:

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

1
Roba fantastica; Se lo usi puts ARGF.readlines.shuffle, puoi farlo funzionare con argomenti sia di input che di nome file stdin.
mklement0,

Ancora più breve ruby -e 'puts $<.sort_by{rand}': ARGF è già un elenco numeroso, quindi possiamo mescolare le linee ordinandole per valori casuali.
Akuhn

6

Un liner per Python basato sulla risposta di Scai , ma a) prende lo stdin, b) rende il risultato ripetibile con seed, c) seleziona solo 200 di tutte le righe.

$ cat file | python -c "import random, sys; 
  random.seed(100); print ''.join(random.sample(sys.stdin.readlines(), 200))," \
  > 200lines.txt

6

Un modo semplice ed intuitivo sarebbe usare shuf.

Esempio:

Assumi words.txtcome:

the
an
linux
ubuntu
life
good
breeze

Per mescolare le linee, fai:

$ shuf words.txt

che avrebbe gettato le linee mescolate in output standard ; Quindi, devi reindirizzarlo a un file di output come:

$ shuf words.txt > shuffled_words.txt

Una corsa di questo tipo potrebbe produrre:

breeze
the
linux
an
ubuntu
good
life

4

Abbiamo un pacchetto per fare proprio il lavoro:

sudo apt-get install randomize-lines

Esempio:

Crea un elenco ordinato di numeri e salvalo in 1000.txt:

seq 1000 > 1000.txt

per mescolarlo, basta usare

rl 1000.txt

3

Questo è uno script Python che ho salvato come rand.py nella mia cartella home:

#!/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()

Su Mac OSX sort -Re shufnon sono disponibili, quindi puoi alias nel tuo bash_profile come:

alias shuf='python rand.py'

3

Se come me sei venuto qui per cercare un'alternativa a shufmacOS, allora usa randomize-lines.

Installa il randomize-linespacchetto (homebrew), che ha un rlcomando con funzionalità simili 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

1
L'installazione di Coreutils con brew install coreutilsfornisce il file shufbinario come gshuf.
Shadowtalker,

2

Se hai installato Scala, ecco un one-liner per mescolare l'input:

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

Incredibilmente semplice, ma a meno che non sia necessario avviare comunque la VM Java, il costo di avvio è considerevole; non funziona bene con conteggi di linee di grandi dimensioni.
mklement0

1

Questa funzione bash ha la dipendenza minima (solo ordinamento 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
}

Bella soluzione bash che è parallela alla awksoluzione assistita dall'OP , ma le prestazioni saranno un problema con input più ampi; l'uso di un singolo $RANDOMvalore mescola correttamente solo fino a 32.768 righe di input; mentre potresti estendere questo intervallo, probabilmente non ne vale la pena: ad esempio, sulla mia macchina, l'esecuzione dello script su 32.768 righe di input brevi richiede circa 1 secondo, ovvero circa 150 volte il tempo di esecuzione shufe circa 10-15 volte fino a quando awkimpiegherà la soluzione assistita dall'OP . Se puoi fare affidamento sulla sortpresenza, awkdovrebbe esserci anche tu .
mklement0,

0

In windows Puoi provare questo file batch per aiutarti a mescolare i tuoi data.txt, L'utilizzo del codice batch è

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

Dopo aver emesso questo comando, maclist_temp.txt conterrà un elenco casuale di righe.

Spero che questo ti aiuti.


Non funziona per file di grandi dimensioni. Mi sono arreso dopo 2 ore per un file di oltre 1 milione di righe
Stefan Haberl,

0

Non ancora menzionato:

  1. L' unsortutilità Sintassi (in qualche modo orientata alla playlist):

    unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic]
           [--identity] [--filenames[=profile]] [--separator sep] [--concatenate] 
           [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null] 
           [--linefeed] [file ...]
  2. msort può mescolare per riga, ma di solito è eccessivo:

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

0

Un'altra 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];
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.