Wie kann ich die Zeilen einer Textdatei in der Unix-Befehlszeile oder in einem Shell-Skript mischen?

285

Ich möchte die Zeilen einer Textdatei zufällig mischen und eine neue Datei erstellen. Die Datei kann mehrere tausend Zeilen enthalten.

Wie kann ich das mit cat, awk, cut, etc?

Ruggiero Spearman
quelle
4
Duplikat von stackoverflow.com/questions/886237/…
Bis auf weiteres angehalten.
Ja, es gibt auch einige andere nette Antworten in dieser ursprünglichen Frage.
Ruggiero Spearman
Also, hast du eine WPA-Wortliste erstellt? (nur eine zufällige Vermutung)
Thahgr

Antworten:

360

Sie können verwenden shuf. Zumindest auf einigen Systemen (scheint nicht in POSIX zu sein).

Wie Jleedev betonte: sort -Rkönnte auch eine Option sein. Zumindest auf einigen Systemen; Nun, Sie bekommen das Bild. Es wurde darauf hingewiesen, dass sort -RElemente nicht wirklich gemischt, sondern nach ihrem Hashwert sortiert werden.

[Anmerkung des Herausgebers: sort -R fast gemischt, außer dass doppelte Zeilen / Sortierschlüssel immer nebeneinander landen . Mit anderen Worten: Nur mit eindeutigen Eingabezeilen / Tasten ist es ein echtes Shuffle. Zwar wird die Ausgabereihenfolge durch Hash-Werte bestimmt , die Zufälligkeit ergibt sich jedoch aus der Auswahl einer zufälligen Hash- Funktion - siehe Handbuch .]

Joey
quelle
31
shufund sort -Runterscheiden sich geringfügig, da sort -Rdie Elemente zufällig nach dem Hash von ihnen geordnet werden sort -R, shufdh die wiederholten Elemente zusammengesetzt werden, während alle Elemente zufällig gemischt werden.
SeMeKh
146
Für OS X-Benutzer: Verwenden Sie brew install coreutilsdann gshuf ...(:
ELLIOTTCABLE
15
sort -Rund shufsollte als völlig anders angesehen werden. sort -Rist deterministisch. Wenn Sie es zweimal zu unterschiedlichen Zeiten am selben Eingang aufrufen, erhalten Sie dieselbe Antwort. shufAuf der anderen Seite wird eine zufällige Ausgabe erzeugt, sodass höchstwahrscheinlich unterschiedliche Ausgaben für dieselbe Eingabe ausgegeben werden.
EfForEffort
18
Das ist nicht richtig. "sort -R" verwendet bei jedem Aufruf einen anderen zufälligen Hash-Schlüssel, sodass jedes Mal eine andere Ausgabe erzeugt wird.
Mark Pettit
3
Hinweis zur Zufälligkeit: In den GNU-Dokumenten heißt es: "Standardmäßig verwenden diese Befehle einen internen Pseudozufallsgenerator, der durch eine geringe Entropie initialisiert wurde, können jedoch angewiesen werden, eine externe Quelle mit der Option --random-source = file zu verwenden."
Royce Williams
85

Perl One-Liner wäre eine einfache Version von Maxim's Lösung

perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile
Moonyoung Kang
quelle
6
Ich habe dies als Alias ​​für OS X verwendet. Danke!
Die Unfun Cat
Dies war das einzige Skript auf dieser Seite, das echte Zufallszeilen zurückgab. Andere awk-Lösungen druckten häufig doppelte Ausgaben.
Felipe Alvarez
1
Aber seien Sie vorsichtig, denn im Out verlieren Sie eine Zeile :) Es wird nur mit einer anderen Zeile verbunden :)
JavaRunner
@ JavaRunner: Ich gehe davon aus, dass Sie über Eingaben ohne Nachstellen sprechen \n. ja, das \nmuss vorhanden sein - und es in der Regel ist - sonst werden Sie bekommen , was Sie beschreiben.
mklement0
1
Wunderbar prägnant. Ich schlage vor , ersetzt <STDIN>mit <>, so dass die Lösung mit dem Input von arbeitet Dateien zu.
mklement0
60

Diese Antwort ergänzt die vielen großen vorhandenen Antworten auf folgende Weise:

  • Die vorhandenen Antworten sind in flexible Shell-Funktionen gepackt :

    • Die Funktionen übernehmen nicht nur stdinEingang, sondern alternativ auch Dateinamen Argumente
    • Die Funktionen erfordern zusätzliche Schritte, um sie wie SIGPIPEgewohnt zu handhaben (leiser Abschluss mit Exit-Code 141), anstatt laut zu brechen. Dies ist wichtig, wenn Sie den Funktionsausgang an ein Rohr weiterleiten, das vorzeitig geschlossen wird, z head.
  • Ein Leistungsvergleich wird durchgeführt.


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))
' "$@"; }

Im unteren Abschnitt finden Sie eine Windows- Version dieser Funktion.

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

Leistungsvergleich:

Hinweis: Diese Zahlen wurden auf einem iMac Ende 2012 mit 3,2 GHz Intel Core i5 und einem Fusion Drive unter OSX 10.10.3 ermittelt. Während die Zeitabläufe je nach verwendetem Betriebssystem, Maschinenspezifikationen und verwendeter awkImplementierung variieren (z. B. ist die awkunter OSX verwendete BSD- Version normalerweise langsamer als GNU awkund insbesondere mawk), sollte dies einen allgemeinen Eindruck von der relativen Leistung vermitteln .

Die Eingabedatei ist eine 1-Millionen-Zeilen-Datei, die mit erstellt wurde seq -f 'line %.0f' 1000000.
Die Zeiten sind in aufsteigender Reihenfolge aufgeführt (am schnellsten zuerst):

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

Zum weiteren Vergleich sind die Lösungen nicht wie oben beschrieben verpackt:

  • sort -R (kein echtes Shuffle, wenn doppelte Eingabezeilen vorhanden sind)
    • 10.661s - Das Zuweisen von mehr Speicher scheint keinen Unterschied zu machen
  • Scala
    • 24.229s
  • bash Schleifen + sort
    • 32.593s

Schlussfolgerungen :

  • Verwenden Sie shuf, wenn Sie können - es ist bei weitem das schnellste.
  • Ruby macht es gut, gefolgt von Perl .
  • Python ist deutlich langsamer als Ruby und Perl, und im Vergleich zu Python-Versionen ist 2.7.6 deutlich schneller als 3.4.1
  • Verwenden Sie die POSIX-kompatible awk+ sort+ cutKombination als letzten Ausweg . Welche awkImplementierung Sie verwenden, ist wichtig ( mawkist schneller als GNU awk, BSD awkist am langsamsten).
  • Bleib weg von sort -R, bashLoops und Scala.

Windows- Versionen der Python- Lösung (der Python-Code ist identisch, mit Ausnahme von Anführungszeichen und dem Entfernen der signalbezogenen Anweisungen, die unter Windows nicht unterstützt werden):

  • Für PowerShell (in Windows PowerShell müssen Sie Anpassungen vornehmen, $OutputEncodingwenn Sie Nicht-ASCII-Zeichen über die Pipeline senden möchten):
# 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  
}

Beachten Sie, dass PowerShell nativ über sein Get-RandomCmdlet mischen kann (obwohl die Leistung ein Problem sein kann). z.B:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)

  • Für cmd.exe(eine Batch-Datei):

In Datei speichern shuf.cmd, zum Beispiel:

@echo off
python -c "import sys, random, fileinput; lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write(''.join(lines))" %*
mklement0
quelle
SIGPIPE existiert unter Windows nicht, daher habe ich stattdessen diesen einfachen 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]));"
Einzeiler
@elig: Danke, aber das Weglassen from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);von der ursprünglichen Lösung ist ausreichend, und behält die Flexibilität auch Dateinamen in der Lage zu übergeben Argumente - keine Notwendigkeit zu ändern etwas anderes (außer zitieren) - Bitte beachten Sie den neuen Abschnitt I bei der hinzugefügt habe Unterseite.
mklement0
27

Ich benutze ein winziges Perl-Skript, das ich "unsortieren" nenne:

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

Ich habe auch eine durch NULL getrennte Version namens "unsort0" ... praktisch für die Verwendung mit find -print0 und so weiter.

PS: Ich habe auch 'shuf' gewählt und hatte keine Ahnung, dass es heutzutage in coreutils gibt ... das oben Genannte kann immer noch nützlich sein, wenn Ihre Systeme kein 'shuf' haben.

NickZoic
quelle
schön, RHEL 5.6 hat keinen Shuf (
Maxim Egorushkin
1
Schön gemacht; Ich schlage vor , ersetzt <STDIN>mit <>, um die Lösung der Arbeit mit dem Input von zu machen Dateien zu.
mklement0
20

Hier ist ein erster Versuch, der für den Codierer einfach, aber für die CPU schwierig ist. Er stellt jeder Zeile eine Zufallszahl voran, sortiert sie und entfernt dann die Zufallszahl von jeder Zeile. Tatsächlich werden die Zeilen zufällig sortiert:

cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled
Ruggiero Spearman
quelle
8
UUOC. Übergeben Sie die Datei an awk selbst.
Ghostdog74
1
Richtig, ich debugge mit head myfile | awk .... Dann ändere ich es einfach in Katze; deshalb wurde es dort gelassen.
Ruggiero Spearman
Keine -k1 -nSortierung erforderlich , da die Ausgabe von awk rand()eine Dezimalstelle zwischen 0 und 1 ist und alles, was zählt, ist, dass sie irgendwie neu angeordnet wird. -k1könnte helfen, es zu beschleunigen, indem der Rest der Zeile ignoriert wird, obwohl die Ausgabe von rand () eindeutig genug sein sollte, um den Vergleich kurzzuschließen.
Bonsaiviking
@ ghostdog74: Die meisten so genannten nutzlosen Verwendungen von cat sind tatsächlich nützlich, um zwischen Pipeline-Befehlen konsistent zu sein und nicht. Es ist besser, das cat filename |(oder < filename |) beizubehalten, als sich daran zu erinnern, wie jedes einzelne Programm Dateieingaben übernimmt (oder nicht).
ShreevatsaR
2
shuf () {awk 'BEGIN {srand ()} {print rand () "\ t" $ 0}' "$ @" | sortieren | cut -f2-;}
Meow
16

Hier ist ein awk-Skript

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

Ausgabe

$ 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
quelle
Schön gemacht, aber in der Praxis viel langsamer als die Antwort des OP , die awkmit sortund kombiniert wird cut. Für nicht mehr als mehrere Tausend Zeilen macht es keinen großen Unterschied, aber bei höheren Zeilenzahlen ist es wichtig (der Schwellenwert hängt von der verwendeten awkImplementierung ab). Eine leichte Vereinfachung wäre das Ersetzen von Zeilen while (1){und if (e==d) {break}durch while (e<d).
mklement0
11

Ein Einzeiler für Python:

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

Und zum Drucken nur einer einzigen zufälligen Zeile:

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

Aber siehe diesen Beitrag für die Nachteile von Python random.shuffle(). Es funktioniert nicht gut mit vielen (mehr als 2080) Elementen.

scai
quelle
2
Der "Nachteil" ist nicht spezifisch für Python. Endliche PRNG-Perioden könnten umgangen werden, indem PRNG wie /dev/urandomdies mit Entropie aus dem System neu ausgesät wird. So verwenden Sie es aus Python : random.SystemRandom().shuffle(L).
JFS
Muss join () nicht auf '\ n' stehen, damit die Zeilen jeweils einzeln gedruckt werden?
Elig
@elig: Nein, da .readLines()die Zeilen mit einem nachgestellten Zeilenumbruch zurückgegeben werden.
mklement0
9

Einfache awk-basierte Funktion erledigt den Job:

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

Verwendung:

any_command | shuffle

Dies sollte unter fast jedem UNIX funktionieren. Getestet unter Linux, Solaris und HP-UX.

Aktualisieren:

Beachten Sie, dass führende Nullen ( %06d) und rand()Multiplikation dazu führen, dass es auch auf Systemen, auf denen sortZahlen nicht verstanden werden, ordnungsgemäß funktioniert . Es kann nach lexikografischer Reihenfolge sortiert werden (auch bekannt als normaler Zeichenfolgenvergleich).

Michał Šrajer
quelle
Gute Idee, die Antwort des OP als Funktion zu verpacken; Wenn Sie anhängen "$@", funktioniert es auch mit Dateien als Eingabe. Es gibt keinen Grund zur Multiplikation rand(), da sort -nDezimalbrüche sortiert werden können. Es ist jedoch eine gute Idee, Kontrolle awk‚s Ausgabeformat, denn mit dem Standardformat, %.6g, rand()ausgeben wird die gelegentliche Zahl in exponentieller Notation. Während das Mischen von bis zu 1 Million Zeilen in der Praxis wohl ausreicht, ist es einfach, mehr Zeilen zu unterstützen, ohne einen großen Leistungsverlust zu zahlen. zB %.17f.
mklement0
1
@ mklement0 Ich habe die Antwort von OPs beim Schreiben meiner nicht bemerkt. rand () wird mit 10e6 multipliziert, damit es, soweit ich mich erinnere, mit Solaris oder HPPP funktioniert. Gute Idee mit "$ @"
Michał Šrajer
1
Habe ich, danke; Vielleicht könnten Sie diese Begründung für die Multiplikation zur Antwort selbst hinzufügen. Laut POSIX sortsollte es im Allgemeinen möglich sein, Dezimalbrüche zu verarbeiten (auch mit Tausenden von Trennzeichen, wie ich gerade bemerkt habe).
mklement0
7

Ruby FTW:

ls | ruby -e 'puts STDIN.readlines.shuffle'
hoffmanc
quelle
1
Tolles Zeug; Wenn Sie verwenden puts ARGF.readlines.shuffle, können Sie dafür sorgen, dass es sowohl mit stdin-Eingabe- als auch mit Dateinamenargumenten funktioniert.
mklement0
Noch kürzer ruby -e 'puts $<.sort_by{rand}'- ARGF ist bereits eine Aufzählung, sodass wir die Zeilen mischen können, indem wir sie nach zufälligen Werten sortieren.
Akuhn
6

Ein Liner für Python basiert auf Scais Antwort , aber a) nimmt stdin, b) macht das Ergebnis mit seed wiederholbar, c) wählt nur 200 aller Zeilen aus.

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

Eine einfache und intuitive Möglichkeit wäre die Verwendung shuf.

Beispiel:

Angenommen, words.txtals:

the
an
linux
ubuntu
life
good
breeze

Um die Zeilen zu mischen, gehen Sie wie folgt vor:

$ shuf words.txt

das würde die gemischten Zeilen auf Standardausgabe werfen ; Also, haben Sie zu Rohr es zu einer Ausgabedatei wie:

$ shuf words.txt > shuffled_words.txt

Ein solcher Shuffle-Lauf könnte ergeben:

breeze
the
linux
an
ubuntu
good
life
kmario23
quelle
4

Wir haben ein Paket für genau diese Aufgabe:

sudo apt-get install randomize-lines

Beispiel:

Erstellen Sie eine geordnete Liste mit Nummern und speichern Sie sie in 1000.txt:

seq 1000 > 1000.txt

Um es zu mischen, verwenden Sie einfach

rl 1000.txt
navigaid
quelle
3

Dies ist ein Python-Skript, das ich als rand.py in meinem Home-Ordner gespeichert habe:

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

Unter Mac OSX sort -Rund shufnicht verfügbar, können Sie dies in Ihrem bash_profile als Alias ​​verwenden:

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

Wenn Sie wie ich hierher gekommen sind, um nach einer Alternative zu shufmacOS zu suchen, dann verwenden Sie randomize-lines.

Installieren Sie das randomize-lines(Homebrew-) Paket, das einen rlBefehl hat, der ähnliche Funktionen hat wie 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
quelle
1
Wenn Sie Coreutils mit installieren, brew install coreutilswird die shufBinärdatei als bereitgestellt gshuf.
Shadowtalker
2

Wenn Sie Scala installiert haben, ist hier ein Einzeiler, um die Eingabe zu mischen:

ls -1 | scala -e 'for (l <- util.Random.shuffle(io.Source.stdin.getLines.toList)) println(l)'
Swartzrock
quelle
Verführerisch einfach, aber wenn die Java-VM nicht trotzdem gestartet werden muss, sind diese Startkosten beträchtlich. funktioniert auch bei großen Zeilenzahlen nicht gut.
mklement0
1

Diese Bash-Funktion hat die minimale Abhängigkeit (nur Sortieren und 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
quelle
Schöne Bash-Lösung, die der vom OP selbst awkunterstützten Lösung entspricht, aber die Leistung wird bei größeren Eingaben ein Problem sein. $RANDOMWenn Sie einen einzelnen Wert verwenden, werden nur bis zu 32.768 Eingabezeilen korrekt gemischt. Sie könnten diesen Bereich zwar erweitern, aber es lohnt sich wahrscheinlich nicht: Auf meinem Computer dauert das Ausführen Ihres Skripts auf 32.768 kurzen Eingabezeilen etwa 1 Sekunde, was etwa 150-mal so lange shufdauert wie das Ausführen , und etwa 10-15 Mal solange die vom OP awkunterstützte Lösung dauert. Wenn Sie sich darauf verlassen können sort, anwesend zu sein, awksollten Sie auch dabei sein.
mklement0
0

In Windows Sie können diese Batch-Datei ausprobieren , um Ihre data.txt zu mischen. Die Verwendung des Batch-Codes ist

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

Nach der Ausgabe dieses Befehls enthält maclist_temp.txt eine zufällige Liste von Zeilen.

Hoffe das hilft.

Ayfan
quelle
Funktioniert nicht für große Dateien. Ich gab nach 2 Stunden für eine Datei mit mehr als 1 Million Zeilen auf
Stefan Haberl
0

Noch nicht erwähnt:

  1. Die unsortutil. Syntax (etwas Playlist-orientiert):

    unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic]
           [--identity] [--filenames[=profile]] [--separator sep] [--concatenate] 
           [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null] 
           [--linefeed] [file ...]
  2. msort kann nach Zeilen mischen, ist aber normalerweise übertrieben:

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

Eine andere 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
quelle