Was ist eine einfache Möglichkeit, zufällige Zeilen aus einer Datei in der Unix-Befehlszeile zu lesen?

263

Was ist eine einfache Möglichkeit, zufällige Zeilen aus einer Datei in der Unix-Befehlszeile zu lesen?

Codeforester
quelle
Ist jede Linie auf eine feste Länge gepolstert?
Tracker1
Nein, jede Zeile hat eine variable Anzahl von Zeichen
große Datei: stackoverflow.com/questions/29102589/…
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

Antworten:

383

Sie können verwenden shuf:

shuf -n 1 $FILE

Es gibt auch ein Dienstprogramm namens rl. In Debian ist es im randomize-linesPaket enthalten, das genau das tut, was Sie wollen, obwohl es nicht in allen Distributionen verfügbar ist. Auf seiner Homepage empfiehlt es tatsächlich die Verwendung von shufstattdessen (was meiner Meinung nach nicht existierte, als es erstellt wurde). shufist Teil der GNU Coreutils, rlnicht.

rl -c 1 $FILE
Rogerdpack
quelle
2
Vielen Dank für den shufTipp, es ist in Fedora integriert.
Cheng
5
Andalo, sort -Rwird auf jeden Fall viel warten lassen, wenn es sich um sehr große Dateien handelt - 80kk Zeilen -, während es shuf -nziemlich augenblicklich funktioniert .
Rubens
23
Sie können shuf unter OS X erhalten, indem Sie es coreutilsvon Homebrew installieren . Könnte gshufstatt genannt werden shuf.
Alyssa Ross
2
Ebenso können Sie randomize-linesunter OS X vonbrew install randomize-lines; rl -c 1 $FILE
Jamie
4
Beachten Sie, dass dies shufTeil von GNU Coreutils ist und daher nicht unbedingt (standardmäßig) auf * BSD-Systemen (oder Mac?) Verfügbar ist. Der Perl-Einzeiler von @ Tracker1 unten ist portabler (und nach meinen Tests etwas schneller).
Adam Katz
74

Eine andere Alternative:

head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1
PolyThinker
quelle
28
$ {RANDOM} generiert nur Zahlen unter 32768, verwenden Sie diese also nicht für große Dateien (z. B. das englische Wörterbuch).
Ralf
3
Aufgrund der Modulo-Operation erhalten Sie nicht für jede Zeile genau die gleiche Wahrscheinlichkeit. Dies spielt kaum eine Rolle, wenn die Dateilänge << 32768 ist (und überhaupt nicht, wenn diese Zahl geteilt wird), ist aber möglicherweise erwähnenswert.
Anaphory
10
Sie können dies mithilfe von auf 30-Bit-Zufallszahlen erweitern (${RANDOM} << 15) + ${RANDOM}. Dies reduziert die Verzerrung erheblich und ermöglicht es, für Dateien mit bis zu 1 Milliarde Zeilen zu arbeiten.
Nneonneo
@nneonneo: Sehr cooler Trick, obwohl es laut diesem Link OR'ing der $ {RANDOM} sein sollte, anstatt stackoverflow.com/a/19602060/293064
Jay Taylor
+und |sind gleich, da ${RANDOM}per Definition 0..32767 ist.
Nneonneo
70
sort --random-sort $FILE | head -n 1

(Ich mag den Shuf-Ansatz oben noch besser - ich wusste nicht einmal, dass es ihn gibt und ich hätte dieses Tool alleine nie gefunden)

Thomas Vander Stichele
quelle
10
+1 Ich mag es, aber Sie benötigen möglicherweise eine sehr aktuelle Version sort, die auf keinem meiner Systeme funktioniert hat (CentOS 5.5, Mac OS 10.7.2). Auch nutzloser Gebrauch von Katze, könnte aufsort --random-sort < $FILE | head -n 1
Steve Kehlet
sort -R <<< $'1\n1\n2' | head -1ist ebenso wahrscheinlich, 1 und 2 zurückzugeben, da sort -Rdoppelte Zeilen zusammen sortiert werden. Gleiches gilt sort -Ru, weil dadurch doppelte Zeilen entfernt werden.
Lri
5
Dies ist relativ langsam, da die gesamte Datei sortvor dem Weiterleiten gemischt werden muss head. shufwählt stattdessen zufällige Zeilen aus der Datei aus und ist für mich viel schneller.
Bengt
1
@SteveKehlet, während wir gerade dabei sind, sort --random-sort $FILE | headwäre am besten, da es ihm ermöglicht, direkt auf die Datei zuzugreifen, was möglicherweise eine effiziente parallele Sortierung ermöglicht
WaelJ
5
Die Optionen --random-sortund -Rsind spezifisch für die GNU-Sortierung (daher funktionieren sie nicht mit BSD oder Mac OS sort). GNU sort hat diese Flags im Jahr 2005 gelernt, sodass Sie GNU Coreutils 6.0 oder neuer (z. B. CentOS 6) benötigen.
RJHunter
31

Das ist einfach.

cat file.txt | shuf -n 1

Zugegeben, dies ist nur ein bisschen langsamer als die "shuf -n 1 file.txt" für sich.

Yokai
quelle
2
Beste Antwort. Ich wusste nichts über diesen Befehl. Beachten Sie, dass -n 11 Zeile angegeben ist und Sie diese in mehr als 1 ändern können. shufKann auch für andere Zwecke verwendet werden. Ich habe gerade gepfeift ps auxund grepdamit zufällig Prozesse beendet, die teilweise mit einem Namen übereinstimmen.
Sudo
18

perlfaq5: Wie wähle ich eine zufällige Zeile aus einer Datei aus? Hier ist ein Reservoir-Sampling-Algorithmus aus dem Camel Book:

perl -e 'srand; rand($.) < 1 && ($line = $_) while <>; print $line;' file

Dies hat einen erheblichen räumlichen Vorteil gegenüber dem Einlesen der gesamten Datei. Einen Beweis für diese Methode finden Sie in The Art of Computer Programming, Band 2, Abschnitt 3.4.2, von Donald E. Knuth.

Tracker1
quelle
1
Nur zum Zwecke der Aufnahme (falls die verwiesene Site ausfällt), hier ist der Code, auf den Tracker1 zeigte: "cat filename | perl -e 'while (<>) {push (@ _, $ _);} print @ _ [rand () * @ _]; '; "
Anirvan
3
Dies ist eine nutzlose Verwendung von Katze. Hier ist eine geringfügige Änderung des Codes in perlfaq5 (und mit freundlicher Genehmigung des Kamelbuchs): perl -e 'srand; rand ($.) <1 && ($ line = $ _) während <>; print $ line; ' Dateiname
Mr. Muskrat
äh ... die verlinkte Seite, das heißt
Nathan Fellman
Ich habe gerade eine N-Linien-Version dieses Codes verglichen shuf. Der Perl-Code ist etwas schneller (8% schneller nach Benutzerzeit, 24% schneller nach Systemzeit), obwohl ich anekdotisch festgestellt habe, dass der Perl-Code weniger zufällig "scheint" (ich habe eine Jukebox damit geschrieben).
Adam Katz
2
Mehr Denkanstöße: shufSpeichert die gesamte Eingabedatei im Speicher , was eine schreckliche Idee ist, während dieser Code nur eine Zeile speichert, sodass die Grenze dieses Codes eine Zeilenanzahl von INT_MAX ist (2 ^ 31 oder 2 ^ 63, abhängig von Ihrer arch), unter der Annahme, dass eine der ausgewählten potenziellen Linien in den Speicher passt.
Adam Katz
11

Verwenden eines Bash-Skripts:

#!/bin/bash
# replace with file to read
FILE=tmp.txt
# count number of lines
NUM=$(wc - l < ${FILE})
# generate random number in range 0-NUM
let X=${RANDOM} % ${NUM} + 1
# extract X-th line
sed -n ${X}p ${FILE}
Paolo Tedesco
quelle
1
Random kann 0 sein, sed benötigt 1 für die erste Zeile. sed -n 0p gibt einen Fehler zurück.
Asalamon74
mhm - wie wäre es mit 1 $ für "tmp.txt" und 2 $ für NUM?
blabla999
Aber selbst mit dem Fehler, der einen Punkt wert ist, da er weder Perl noch Python benötigt und so effizient wie möglich ist (die Datei genau zweimal lesen, aber nicht in den Speicher - also würde es auch mit großen Dateien funktionieren).
blabla999
@ asalamon74: danke @ blabla999: wenn wir eine Funktion daraus machen, ok für $ 1, aber warum nicht NUM berechnen?
Paolo Tedesco
Ändern der sed-Zeile in: head - $ {X} $ {FILE} | Schwanz -1 sollte es tun
JeffK
4

Einzelne Bash-Linie:

sed -n $((1+$RANDOM%`wc -l test.txt | cut -f 1 -d ' '`))p test.txt

Leichtes Problem: doppelter Dateiname.

asalamon74
quelle
2
leichteres Problem. Wenn Sie dies für / usr / share / dict / words ausführen, werden Wörter bevorzugt, die mit "A" beginnen. Wenn ich damit spiele, habe ich ungefähr 90% "A" -Wörter bis 10% "B" -Wörter. Noch keine, die mit Zahlen beginnen, die den Kopf der Datei bilden.
Bibby
wc -l < test.txtvermeidet das Weiterleiten cut.
Fedorqui 'SO hör auf zu schaden'
3

Hier ist ein einfaches Python-Skript, das die Arbeit erledigt:

import random, sys
lines = open(sys.argv[1]).readlines()
print(lines[random.randrange(len(lines))])

Verwendungszweck:

python randline.py file_to_get_random_line_from
Adam Rosenfield
quelle
1
Das funktioniert nicht ganz. Es stoppt nach einer einzelnen Zeile. Damit es funktioniert, habe ich import random, sys lines = open(sys.argv[1]).readlines() Folgendes getan: für i im Bereich (len (Linien)): rand = random.randint (0, len (Linien) -1) print lines.pop (rand),
Jed Daniels
Dummes Kommentarsystem mit beschissener Formatierung. Hat das Formatieren in Kommentaren nicht einmal funktioniert?
Jed Daniels
Randint ist inklusive und len(lines)kann daher zu IndexError führen. Sie könnten verwenden print(random.choice(list(open(sys.argv[1])))). Es gibt auch einen speichereffizienten Reservoir-Abtastalgorithmus .
JFS
2
Ziemlich platzhungrig; Betrachten Sie eine 3-TB-Datei.
Michael Campbell
@MichaelCampbell: Der oben erwähnte Reservoir-Sampling-Algorithmus funktioniert möglicherweise mit 3-TB-Dateien (wenn die Zeilengröße begrenzt ist).
JFS
2

Ein anderer Weg mit ' awk '

awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name
Baskar
quelle
2
Das benutzt awk und bash ( $RANDOMist ein Bashismus ). Hier ist eine reine awk (mawk) -Methode, die dieselbe Logik wie der oben zitierte Perlfaq5-Code von @ Tracker1 verwendet: awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(Wow, es ist sogar noch kürzer als der Perl-Code!)
Adam Katz
Dieser Code muss die Datei ( wc) lesen , um eine Zeilenanzahl zu erhalten, und muss dann (einen Teil) der Datei erneut lesen ( awk), um den Inhalt der angegebenen zufälligen Zeilennummer zu erhalten. E / A sind weitaus teurer als das Erhalten einer Zufallszahl. Mein Code liest die Datei nur einmal. Das Problem bei awk's rand()ist, dass es auf Sekunden basiert, sodass Sie Duplikate erhalten, wenn Sie es nacheinander zu schnell ausführen.
Adam Katz
1

Eine Lösung, die auch unter MacOSX funktioniert und auch unter Linux (?) Funktionieren sollte:

N=5
awk 'NR==FNR {lineN[$1]; next}(FNR in lineN)' <(jot -r $N 1 $(wc -l < $file)) $file 

Wo:

  • N ist die Anzahl der gewünschten zufälligen Zeilen

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 -> Speichern Sie die eingegebenen Zeilennummern file1und drucken Sie die entsprechende Zeile einfile2

  • jot -r $N 1 $(wc -l < $file)-> NZahlen zufällig ( -r) im Bereich (1, number_of_line_in_file)mit zeichnen jot. Durch die Prozessersetzung <()sieht es wie eine Datei für den Interpreter aus, also file1im vorherigen Beispiel.
jrjc
quelle
0
#!/bin/bash

IFS=$'\n' wordsArray=($(<$1))

numWords=${#wordsArray[@]}
sizeOfNumWords=${#numWords}

while [ True ]
do
    for ((i=0; i<$sizeOfNumWords; i++))
    do
        let ranNumArray[$i]=$(( ( $RANDOM % 10 )  + 1 ))-1
        ranNumStr="$ranNumStr${ranNumArray[$i]}"
    done
    if [ $ranNumStr -le $numWords ]
    then
        break
    fi
    ranNumStr=""
done

noLeadZeroStr=$((10#$ranNumStr))
echo ${wordsArray[$noLeadZeroStr]}
Ken
quelle
Da $ RANDOM Zahlen generiert, die kleiner sind als die Anzahl der Wörter in / usr / share / dict / words (235886) (auf meinem Mac sowieso), generiere ich nur 6 separate Zufallszahlen zwischen 0 und 9 und reihe sie aneinander. Dann stelle ich sicher, dass die Zahl kleiner als 235886 ist. Entfernen Sie dann führende Nullen, um die Wörter zu indizieren, die ich im Array gespeichert habe. Da jedes Wort eine eigene Zeile ist, kann dies leicht für jede Datei verwendet werden, um zufällig eine Zeile auszuwählen.
Ken
0

Folgendes entdecke ich, da mein Mac OS nicht alle einfachen Antworten verwendet. Ich habe den Befehl jot verwendet, um eine Zahl zu generieren, da die variablen Lösungen von $ RANDOM in meinem Test nicht sehr zufällig zu sein scheinen. Beim Testen meiner Lösung gab es große Unterschiede bei den in der Ausgabe angegebenen Lösungen.

  RANDOM1=`jot -r 1 1 235886`
   #range of jot ( 1 235886 ) found from earlier wc -w /usr/share/dict/web2
   echo $RANDOM1
   head -n $RANDOM1 /usr/share/dict/web2 | tail -n 1

Das Echo der Variablen dient dazu, eine visuelle Darstellung der generierten Zufallszahl zu erhalten.

dreday13
quelle
0

Wenn Sie nur Vanille sed und awk verwenden und $ RANDOM nicht verwenden, ist ein einfacher, platzsparender und relativ schneller "Einzeiler" zum Auswählen einer einzelnen Zeile pseudozufällig aus einer Datei mit dem Namen FILENAME wie folgt:

sed -n $(awk 'END {srand(); r=rand()*NR; if (r<NR) {sub(/\..*/,"",r); r++;}; print r}' FILENAME)p FILENAME

(Dies funktioniert auch, wenn FILENAME leer ist. In diesem Fall wird keine Zeile ausgegeben.)

Ein möglicher Vorteil dieses Ansatzes besteht darin, dass rand () nur einmal aufgerufen wird.

Wie von @AdamKatz in den Kommentaren hervorgehoben, besteht eine andere Möglichkeit darin, rand () für jede Zeile aufzurufen:

awk 'rand() * NR < 1 { line = $0 } END { print line }' FILENAME

(Ein einfacher Korrektheitsnachweis kann auf der Grundlage der Induktion erbracht werden.)

Vorbehalt über rand()

"In den meisten awk-Implementierungen, einschließlich gawk, generiert rand () jedes Mal, wenn Sie awk ausführen, Zahlen aus derselben Startnummer oder demselben Startwert."

- https://www.gnu.org/software/gawk/manual/html_node/Numeric-Functions.html

Gipfel
quelle
Siehe den Kommentar, den ich ein Jahr vor dieser Antwort gepostet habe , der eine einfachere awk-Lösung enthält, für die sed nicht erforderlich ist. Beachten Sie auch meine Einschränkung bezüglich des Zufallszahlengenerators von awk, der in ganzen Sekunden sät.
Adam Katz