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?



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


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

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))" %*

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.


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

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

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.


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


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'

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

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

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

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'

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

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

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
}

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.


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

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];
  }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.