Jak wyświetlić losową linię z pliku tekstowego?

26

Próbuję napisać skrypt powłoki. Chodzi o to, aby wybrać losowo pojedynczą linię z pliku tekstowego i wyświetlić ją jako powiadomienie na pulpicie Ubuntu.

Ale chcę, aby przy każdym uruchomieniu skryptu były wybierane różne wiersze. Czy jest na to jakieś rozwiązanie? Nie chcę całego skryptu. Tylko ta prosta rzecz.

Anandu M Das
źródło
Odwiedź także: askubuntu.com/q/492572/256099
Pandya
stackoverflow.com/questions/448005/…
Ciro Santilli 20 改造 中心 法轮功 六四 事件

Odpowiedzi:

40

Możesz użyć shufnarzędzia do drukowania losowych linii z pliku

$ shuf -n 1 filename

-n : liczba linii do wydrukowania

Przykłady:

$ 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
aneeshep
źródło
Ale używając tego, muszę ręcznie zmienić wartość n, prawda? Chcę, aby powłoka automatycznie wybierała kolejną linię losowo. Nie trzeba być losowym. Ale jakaś inna linia.
Anandu M Das
4
@AnanduMDas Nie nie musisz oznaczać nliczby wierszy do wydrukowania. (tj. czy chcesz tylko jedną linię czy dwie linie). Nie numer wiersza (tj. Pierwszy wiersz, drugi wiersz).
aneeshep
@AnanduMDas: Dodałem kilka przykładów do mojej odpowiedzi. Mam nadzieję, że teraz wszystko jest jasne.
aneeshep
1
Dziękuję, że teraz jest jasne :) Znalazłem również inny algorytm, podobnie jak, zapisz bieżący czas (tylko drugi, przez date +%S) w zmiennej x, a następnie wybierz tę X linię za pomocą poleceń headi tailz pliku tekstowego. W każdym razie twoja metoda jest łatwiejsza. Dzięki
Anandu M. Das
+1: shufjest w coreutils, więc jest dostępny domyślnie. Uwaga: ładuje plik wejściowy do pamięci. Istnieje skuteczny algorytm, który go nie wymaga .
jfs
13

Możesz także użyć sortpolecenia, aby uzyskać losową linię z pliku.

sort -R filename | head -n1
g_p
źródło
Uwaga: sort -Rdaje inny wynik niż shuf -n1lub select-randomjeśli na wejściu znajdują się zduplikowane linie. Zobacz komentarz @ EliahKagan .
jfs
8

Tak dla zabawy, tutaj jest czystą rozwiązanie bash , który nie korzysta shuf, sort, wc, sed, head, taillub jakiekolwiek inne narzędzia zewnętrzne.

Jedyna przewaga nad shuf wariantem jest to, że jest nieco szybszy, ponieważ jest czystym uderzeniem. Na moim komputerze, dla pliku 1000 linii shufwariant zajmuje około 0,1 sekundy, podczas gdy poniższy skrypt zajmuje około 0,01 sekundy;) Tak więc, chociaż shufjest to najłatwiejszy i najkrótszy wariant, jest on szybszy.

Szczerze mówiąc nadal wybrałbym shufrozwiązanie, chyba że wysoka wydajność jest ważnym problemem.

#!/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"
Malte Skoruppa
źródło
@EliahKagan Dzięki za sugestie i dobre punkty. Przyznaję, że jest całkiem sporo narożnych spraw, o których tak naprawdę nie myślałem zbyt wiele. Naprawdę napisałem to dla zabawy. Korzystanie shufjest znacznie lepsze. Myśląc o tym, nie wierzę, że czysty bash jest tak naprawdę bardziej wydajny niż używanie shuf, jak wcześniej pisałem. Podczas uruchamiania zewnętrznego narzędzia może być najmniejszy (stały) narzut, ale wtedy będzie on działał mach szybciej niż interpretowane bash. Więc na shufpewno skaluje się lepiej. Powiedzmy, że skrypt służy celowi edukacyjnemu: Miło jest widzieć, że da się to zrobić;)
Malte Skoruppa
GNU / Linux / Un * x ma wiele bardzo dobrze przetestowanych kół, których nie chciałbym wymyślać od nowa, chyba że byłyby to ćwiczenia czysto akademickie. „Powłoka” została zaprojektowana do montażu wielu małych, istniejących części, które można (ponownie) złożyć na różne sposoby za pomocą opcji wejścia / wyjścia i wielu innych opcji. Wszystko inne jest złej formie, chyba że jest przeznaczone do sportu (np. Codegolf.stackexchange.com/tour ), w takim przypadku graj na ...!
Michael
2
@michael_n Chociaż metoda „czystego bashu” jest przydatna głównie do nauczania i modyfikowania innych zadań, jest to bardziej rozsądna implementacja „rzeczywista”, niż może się wydawać. Bash jest powszechnie dostępny, ale shufspecyficzny dla GNU Coreutils (np. Nie we FreeBSD 10.0). sort -Rjest przenośny, ale rozwiązuje inny (związany) problem: ciągi pojawiające się jako wiele linii mają prawdopodobieństwo równe tym, które pojawiają się tylko raz. (Oczywiście, wci inne narzędzia mogą być nadal używane.) Myślę, że głównym ograniczeniem tutaj jest to, że nigdy nie wybiera niczego po 32768 linii (i staje się mniej losowy nieco wcześniej).
Eliah Kagan,
2
Malte Skoruppa: Widzę, że przeniosłeś pytanie dotyczące PRNG do U&L . Fajne. Podpowiedź: $((RANDOM<<15|RANDOM))jest w zakresie 0..2 ^ 30-1. @JFSebastian To shufnie jest tak sort -R, że skłania się ku częstszym wejściom. Umieścić shuf -n 1w miejscu sort -R | head -n1i porównać. (Btw 10 ^ 3 iteracje są szybsze niż 10 ^ 6 i wciąż wystarczają, aby pokazać różnicę.) Zobacz także bardziej surowe, bardziej wizualne demo i ten odrobinę głupoty pokazujący, że działa na dużych wejściach, gdzie wszystkie struny mają wysoką częstotliwość .
Eliah Kagan
1
@JFSebastian W tym poleceniu dane wejściowe dieharderwydają się składać się z samych zer. Zakładając, że nie jest to po prostu jakaś dziwna pomyłka z mojej strony, to z pewnością wyjaśnia, dlaczego nie jest przypadkowa! Czy otrzymujesz dobrze wyglądające dane, jeśli biegniesz while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > outprzez chwilę, a następnie sprawdzasz zawartość outedytora szesnastkowego? (Albo go zobaczyć jednak inny lubisz.) Pojawia się same zera, a RANDOMnie jest winowajcą: mam same zera, gdy zastąpi $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))się 100też.
Eliah Kagan
4

Powiedz, że masz plik notifications.txt. Musimy policzyć całkowitą liczbę linii, aby określić zakres generatora losowego:

$ cat notifications.txt | wc -l

Napiszmy do zmiennej:

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

Teraz do wygenerowania liczby od 0do $LINEużyjemy RANDOMzmiennej.

$ echo $[ $RANDOM % LINES]

Napiszmy do zmiennej:

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

Teraz musimy tylko wydrukować ten numer wiersza:

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

O firmie 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.

Upewnij się, że plik ma mniej niż 32767 numerów linii. Zobacz to, jeśli potrzebujesz większego generatora losowego, który działa od razu po wyjęciu z pudełka.

Przykład:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '
c0rp
źródło
Alternatywa stylistyczna (bash):LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
Michael
np. spójrz na ostatnie zdjęcie w Testowym PRNG, używając szarej mapy bitowej, aby zrozumieć, dlaczego nie jest dobrym pomysłem zastosowanie % ndo liczby losowej.
jfs
2

Oto skrypt w języku Python, który wybiera losową linię z plików wejściowych lub standardowego wejścia:

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

Algorytm to czas O (n), przestrzeń O (1). Działa dla plików większych niż 32767 linii. Nie ładuje plików wejściowych do pamięci. Czyta każdą linię wejściową dokładnie raz, tzn. Można do niej wstawić dowolną dużą (ale skończoną) treść. Oto wyjaśnienie algorytmu .

jfs
źródło
1

Jestem pod wrażeniem pracy, którą wykonała Malte Skoruppa i inni, ale tutaj jest o wiele prostszy sposób „czystej bash”:

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

Jak niektórzy zauważyli, $ RANDOM nie jest przypadkowy. Jednak limit rozmiaru pliku wynoszący 32767 wierszy zostaje pokonany przez połączenie łańcuchów $ RANDOM w razie potrzeby.

Nicpoń
źródło