Wie zeige ich eine zufällige Zeile aus einer Textdatei an?


26

Ich versuche ein Shell-Skript zu schreiben. Die Idee ist, eine einzelne Zeile zufällig aus einer Textdatei auszuwählen und sie als Ubuntu-Desktop-Benachrichtigung anzuzeigen.

Ich möchte jedoch, dass bei jeder Ausführung des Skripts andere Zeilen ausgewählt werden. Gibt es dafür eine Lösung? Ich möchte nicht das gesamte Drehbuch. Nur diese einfache Sache.



Antworten:


40

Sie können das shufDienstprogramm verwenden, um zufällige Zeilen aus einer Datei zu drucken

$ shuf -n 1 filename

-n : Anzahl der zu druckenden Zeilen

Beispiele:

$ shuf -n 1 /etc/passwd

git:x:998:998:git daemon user:/:/bin/bash

$ shuf -n 2 /etc/passwd

avahi:x:84:84:avahi:/:/bin/false
daemon:x:2:2:daemon:/sbin:/bin/false

Aber wenn ich das benutze, muss ich den Wert von n manuell ändern, oder? Ich möchte, dass diese Shell automatisch eine andere Zeile zufällig auswählt. Nicht unbedingt zufällig. Aber eine andere Linie.
Anandu M Das

4
@AnanduMDas Nein, Sie müssen nicht ndie Anzahl der zu druckenden Zeilen angeben . (dh ob Sie nur eine Zeile oder zwei Zeilen möchten). Nicht die Zeilennummer (dh erste Zeile 2. Zeile).
Aneeshep

@AnanduMDas: Ich habe meiner Antwort einige Beispiele hinzugefügt. Hoffe es ist jetzt klar.
Aneeshep

1
Vielen Dank, es ist jetzt klar :) Ich habe auch einen anderen Algorithmus gefunden, der wie date +%Sfolgt aussieht : Speichere die aktuelle Zeit (nur die Sekunde, von ) in einer Variablen x und wähle dann diese x-te Zeile mit den Befehlen headund tailaus der Textdatei aus. Auf jeden Fall ist Ihre Methode einfacher. Danke
Anandu M Das

+1: shufBefindet sich in coreutils, ist also standardmäßig verfügbar. Hinweis: Die Eingabedatei wird in den Speicher geladen. Es gibt einen effizienten Algorithmus, der dies nicht erfordert .
jfs


8

Just for fun, hier ist eine reine bash - Lösung , die nicht verwendet shuf, sort, wc, sed, head, tailoder andere externe Tools.

Der einzige Vorteil gegenüber der shufVariante ist, dass es etwas schneller ist, da es reine Bash ist. Auf meinem Computer shufdauert die Variante für eine Datei mit 1000 Zeilen ungefähr 0,1 Sekunden, während das folgende Skript ungefähr 0,01 Sekunden dauert shuf.

Ganz ehrlich, ich würde mich immer noch für die shufLösung entscheiden, es sei denn, hohe Effizienz ist ein wichtiges Anliegen.

#!/bin/bash

FILE=file.txt

# get line count for $FILE (simulate 'wc -l')
lc=0
while read -r line; do
 ((lc++))
done < $FILE

# get a random number between 1 and $lc
rnd=$RANDOM
let "rnd %= $lc"
((rnd++))

# traverse file and find line number $rnd
i=0
while read -r line; do
 ((i++))
 [ $i -eq $rnd ] && break
done < $FILE

# output random line
printf '%s\n' "$line"

@EliahKagan Danke für die Anregungen und guten Punkte. Ich gebe zu, es gibt einige Eckfälle, über die ich nicht wirklich nachgedacht hatte. Ich habe das wirklich mehr zum Spaß geschrieben. Verwenden shufist sowieso viel besser. Wenn ich daran denke, glaube ich nicht, dass reines Bash tatsächlich effizienter ist als das Verwenden shuf, wie ich zuvor geschrieben habe. Es kann den kleinsten (konstanten) Overhead geben, wenn ein externes Tool gestartet wird, aber dann läuft es mach schneller als interpretierte Bash. Skaliert also shufsicher besser. Nehmen wir also an, das Drehbuch dient einem pädagogischen Zweck: Es ist schön zu sehen, dass es getan werden kann;)
Malte Skoruppa

GNU / Linux / Un * x hat viele sehr gut getestete Räder, die ich nicht neu erfinden möchte, es sei denn, es war eine rein akademische Übung. Die "Schale" sollte verwendet werden, um viele kleine existierende Teile zusammenzusetzen, die auf verschiedene Arten über Eingabe / Ausgabe & viele Optionen (wieder) zusammengebaut werden konnten. Alles andere ist eine schlechte Form, es sei denn, es handelt sich um Sport (z. B. codegolf.stackexchange.com/tour ). Spielen Sie in diesem Fall weiter ...!
Michael

2
@michael_n Obwohl eine "pure bash" Methode hauptsächlich zum Lehren und Ändern für andere Aufgaben nützlich ist, ist dies eine vernünftigere "echte" Implementierung, als es scheint. Bash ist weit verbreitet, aber shufGNU Coreutils-spezifisch (z. B. nicht in FreeBSD 10.0). sort -Rist portabel, löst jedoch ein anderes (verwandtes) Problem: Zeichenfolgen, die als mehrere Zeilen angezeigt werden, haben eine Wahrscheinlichkeit, die der Wahrscheinlichkeit entspricht, die nur einmal angezeigt wird. (Natürlich wcund andere Dienstprogramme könnten noch verwendet werden.) Ich denke, die Haupteinschränkung ist, dass hier niemals etwas nach der 32768-Zeile ausgewählt wird (und etwas früher weniger zufällig wird).
Eliah Kagan

2
Malte Skoruppa: Ich sehe, Sie haben die PRNG-Frage an U & L weitergeleitet . Cool. Hinweis: $((RANDOM<<15|RANDOM))ist in 0..2 ^ 30-1. @JFSebastian Es ist shufnicht so sort -R, dass es zu häufigeren Eingaben kommt. Anstelle shuf -n 1von setzen sort -R | head -n1und vergleichen. (Btw 10 ^ 3 Iterationen ist schneller als 10 ^ 6 und noch recht genug , um den Unterschied zu zeigen.) Siehe auch eine rauere, mehr visuelle Demo und dieses Stück silliness zeigt es auf großen Eingängen arbeitet , wo alle Strings Hochfrequenz sind .
Eliah Kagan

1
@JFSebastian In diesem Befehl dieharderscheint die Eingabe für alle Nullen zu sein. Vorausgesetzt, dies ist nicht nur ein merkwürdiger Fehler von meiner Seite, würde das sicherlich erklären, warum es nicht zufällig ist! Erhalten Sie gut aussehende Daten, wenn Sie while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > outeine Weile laufen und dann den Inhalt outmit einem Hex-Editor untersuchen? (Oder sehen Sie es jedoch , was Sie mögen.) Ich Nullen erhalten, und RANDOMist nicht der Schuldige: ich alle Nullen, wenn ich ersetzen $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))mit 100, auch.
Eliah Kagan

4

Angenommen, Sie haben eine Datei notifications.txt. Wir müssen die Gesamtzahl der Zeilen zählen, um die Reichweite des Zufallsgenerators zu bestimmen:

$ cat notifications.txt | wc -l

Schreiben wir in die Variable:

$ LINES=$(cat notifications.txt | wc -l)

Um nun eine Zahl von 0bis zu generieren , verwenden $LINEwir eine RANDOMVariable.

$ echo $[ $RANDOM % LINES]

Schreiben wir es in die Variable:

$  R_LINE=$(($RANDOM % LINES))

Jetzt müssen wir nur noch diese Zeilennummer drucken:

$ sed -n "${R_LINE}p" notifications.txt

Über RANDOM:

   RANDOM Each time this parameter is referenced, a random integer between
          0 and 32767 is generated.  The sequence of random numbers may be
          initialized by assigning a value to RANDOM.  If RANDOM is unset,
          it  loses  its  special  properties,  even if it is subsequently
          reset.

Stellen Sie sicher, dass Ihre Datei weniger als 32767 Zeilennummern hat. Sehen Sie dies, wenn Sie einen größeren Zufallsgenerator benötigen, der sofort einsatzbereit ist.

Beispiel:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '

Eine stilistische Alternative (bash):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
Michael


Schauen Sie sich zum Beispiel das letzte Bild in Test PRNG mit einer grauen Bitmap an, um zu verstehen, warum es keine gute Idee ist % n, eine Zufallszahl zu verwenden.
jfs

2

Hier ist ein Python-Skript, das eine zufällige Zeile aus Eingabedateien oder stdin auswählt:

#!/usr/bin/env python
"""Usage: select-random [<file>]..."""
import random

def select_random(iterable, default=None, random=random):
    """Select a random element from iterable.

    Return default if iterable is empty.
    If iterable is a sequence then random.choice() is used for efficiency instead.
    If iterable is an iterator; it is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    try:
        return random.choice(iterable) # O(1) time and space
    except IndexError: # empty sequence
        return default
    except TypeError: # not a sequence
        return select_random_it(iter(iterable), default, random.randrange)

def select_random_it(iterator, default=None, randrange=random.randrange):
    """Return a random element from iterator.

    Return default if iterator is empty.
    iterator is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    # from /programming//a/1456750/4279
    # select 1st item with probability 100% (if input is one item, return it)
    # select 2nd item with probability 50% (or 50% the selection stays the 1st)
    # select 3rd item with probability 33.(3)%
    # select nth item with probability 1/n
    selection = default
    for i, item in enumerate(iterator, start=1):
        if randrange(i) == 0: # random [0..i)
            selection = item
    return selection

if __name__ == "__main__":
    import fileinput
    import sys

    random_line = select_random_it(fileinput.input(), '\n')
    sys.stdout.write(random_line)
    if not random_line.endswith('\n'):
        sys.stdout.write('\n') # always append newline at the end

Der Algorithmus ist O (n) -Zeit, O (1) -Raum. Es funktioniert für Dateien mit mehr als 32767 Zeilen. Eingabedateien werden nicht in den Speicher geladen. Es liest jede Eingabezeile genau einmal, dh, Sie können beliebig große (aber endliche) Inhalte einlesen. Hier ist eine Erklärung des Algorithmus .


1

Ich bin beeindruckt von der Arbeit, die Malte Skoruppa und andere geleistet haben, aber hier ist eine viel einfachere "pure bash" -Methode:

IFS=$'\012'
# set field separator to newline only
lines=( $(<test5) )
# slurp entire file into an array
numlines=${#lines[@]}
# count the array elements
num=$(( $RANDOM$RANDOM$RANDOM % numlines ))
# get a (more-or-less) random number within the correct range
line=${lines[$num]}
# select the element corresponding to the random number
echo $line
# display it

Wie einige angemerkt haben, ist $ RANDOM nicht zufällig. Die Dateigrößenbeschränkung von 32767 Zeilen wird jedoch durch Aneinanderreihen von $ RANDOMs nach Bedarf überwunden.

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.