Jaki jest łatwy sposób na odczytanie losowej linii z pliku w linii poleceń Uniksa?

263

Jaki jest łatwy sposób na odczytanie losowej linii z pliku w linii poleceń Uniksa?

codeforester
źródło
Czy każda linia jest wyściełana na określoną długość?
Tracker1
nie, każda linia ma zmienną liczbę znaków
duży plik: stackoverflow.com/questions/29102589/...
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

Odpowiedzi:

383

Możesz użyć shuf :

shuf -n 1 $FILE

Istnieje również narzędzie o nazwie rl. W Debianie jest w randomize-linespakiecie, który robi dokładnie to, co chcesz, choć nie jest dostępny we wszystkich dystrybucjach. Na swojej stronie głównej zaleca używanie shufzamiast niej (jak sądzę, która nie istniała podczas jej tworzenia). shufjest częścią jądra GNU, rlnie jest.

rl -c 1 $FILE
rogerdpack
źródło
2
Dzięki za shufwskazówkę, jest wbudowany w Fedorę.
Cheng
5
Andalso z sort -Rpewnością sprawi, że będziesz musiał dużo czekać, jeśli poradzisz sobie z bardzo dużymi plikami - 80kk linii - podczas gdy shuf -ndziała dość natychmiastowo.
Rubens
23
Możesz uzyskać shuf na OS X, instalując coreutilsz Homebrew. Może być wywołany gshufzamiast shuf.
Alyssa Ross,
2
Podobnie możesz używać randomize-linesw systemie OS X przezbrew install randomize-lines; rl -c 1 $FILE
Jamie
4
Zauważ, że shufjest częścią GNU Coreutils i dlatego niekoniecznie będzie dostępny (domyślnie) w systemach * BSD (lub Mac?). @ Perl one-liner poniżej programu Tracker1 jest bardziej przenośny (i według moich testów jest nieco szybszy).
Adam Katz,
74

Inna alternatywa:

head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1
PolyThinker
źródło
28
$ {RANDOM} generuje tylko liczby mniejsze niż 32768, więc nie używaj tego w przypadku dużych plików (na przykład słownika angielskiego).
Ralf
3
To nie daje dokładnie takiego samego prawdopodobieństwa dla każdej linii, ze względu na działanie modulo. Nie ma to znaczenia, czy długość pliku wynosi << 32768 (i wcale nie dzieli tej liczby), ale może warto to zauważyć.
Anafora,
10
Możesz rozszerzyć to do 30-bitowych liczb losowych za pomocą (${RANDOM} << 15) + ${RANDOM}. To znacznie zmniejsza obciążenie i pozwala na pracę z plikami zawierającymi do 1 miliarda linii.
nneonneo
@nneonneo: Bardzo fajna sztuczka, chociaż zgodnie z tym linkiem powinno to być OR'owanie $ {RANDOM} zamiast PLUS'ing stackoverflow.com/a/19602060/293064
Jay Taylor
+i |są takie same, ponieważ ${RANDOM}z definicji ma wartość 0..32767.
nneonneo
71
sort --random-sort $FILE | head -n 1

(Jeszcze bardziej podoba mi się powyższe podejście shuf - nawet nie wiedziałem, że istnieje i nigdy nie znalazłbym tego narzędzia na własną rękę)

Thomas Vander Stichele
źródło
10
+1 Podoba mi się, ale może potrzebujesz najnowszej wersji sort, nie działał na żadnym z moich systemów (CentOS 5.5, Mac OS 10.7.2). Również bezużyteczne użycie kota można ograniczyć dosort --random-sort < $FILE | head -n 1
Steve
sort -R <<< $'1\n1\n2' | head -1prawdopodobnie zwróci 1 i 2, ponieważ sort -Rsortuje zduplikowane linie razem. To samo dotyczy sort -Ru, ponieważ usuwa duplikaty linii.
Lri
5
Jest to stosunkowo powolne, ponieważ cały plik musi zostać przetasowany sortprzed przesłaniem go do potoku head. shufzamiast tego wybiera losowe linie z pliku i jest dla mnie znacznie szybszy.
Bengt,
1
@SteveKehlet, gdy jesteśmy przy tym, sort --random-sort $FILE | headbyłoby najlepiej, ponieważ pozwala on na bezpośredni dostęp do pliku, prawdopodobnie umożliwiając wydajne sortowanie równoległe
WaelJ
5
The --random-sortI -Ropcje są specyficzne dla GNU sort (więc nie będzie działać z BSD i Mac OS sort). Sortowanie GNU nauczyło się tych flag w 2005 roku, więc potrzebujesz GNU coreutils 6.0 lub nowszego (np. CentOS 6).
RJHunter,
31

To jest proste.

cat file.txt | shuf -n 1

To prawda, że ​​jest to tylko odrobinę wolniej niż sam plik „shuf -n 1 file.txt”.

Jokai
źródło
2
Najlepsza odpowiedź. Nie wiedziałem o tym poleceniu. Zauważ, że -n 1określa 1 linię, i możesz zmienić ją na więcej niż 1. shufMożna jej również użyć do innych rzeczy; Właśnie wykonałem potok ps auxi grepza jego pomocą losowo zabiłem procesy częściowo pasujące do nazwy.
sudo
18

perlfaq5: Jak wybrać losową linię z pliku? Oto algorytm próbkowania zbiornika z Księgi wielbłądów:

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

Ma to znaczną przewagę w przestrzeni kosmicznej nad wczytywaniem całego pliku. Dowód tej metody można znaleźć w The Art of Computer Programming, Tom 2, Rozdział 3.4.2, Donalda E. Knutha.

Tracker 1
źródło
1
Tylko w celu włączenia (na wypadek, gdyby odnośna strona przestała działać), oto kod wskazany przez Tracker1: „cat filename | perl -e 'while (<>) {push (@ _, $ _);} print @ _[skraj()*@_];';"
Anirvan
3
Jest to bezużyteczne użycie kota. Oto niewielka modyfikacja kodu znalezionego w perlfaq5 (i dzięki uprzejmości książki Camel): perl -e 'srand; rand ($.) <1 && ($ line = $ _) while <>; print $ line; ' nazwa pliku
Mr. Muskrat
err ... linkowana strona, czyli
Nathan Fellman
Właśnie porównałem wersję tego kodu z N-liniami shuf. Kod perlowy jest bardzo nieznacznie szybszy (8% szybszy w czasie użytkownika, 24% szybszy w czasie systemowym), choć anegdotycznie stwierdziłem, że kod perla „wydaje się” mniej przypadkowy (napisałem z niego szafę grającą).
Adam Katz,
2
Więcej do myślenia: shufprzechowuje cały plik wejściowy w pamięci , co jest okropnym pomysłem, podczas gdy ten kod przechowuje tylko jedną linię, więc limit tego kodu to liczba linii INT_MAX (2 ^ 31 lub 2 ^ 63 w zależności od twojego arch), zakładając, że dowolna z wybranych linii potencjalnych mieści się w pamięci.
Adam Katz,
11

za pomocą skryptu bash:

#!/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
źródło
1
Losowo może wynosić 0, sed potrzebuje 1 dla pierwszego wiersza. sed -n 0p zwraca błąd.
asalamon74
mhm - a może 1 USD za „tmp.txt” i 2 USD za NUM?
blabla999
ale nawet z błędem wartym uwagi, ponieważ nie wymaga Perla ani Pythona i jest tak wydajny, jak to tylko możliwe (czytanie pliku dokładnie dwa razy, ale nie do pamięci - więc działałoby nawet przy dużych plikach).
blabla999
@ asalamon74: thanks @ blabla999: jeśli zrobimy z niego jakąś funkcję, ok za 1 $, ale dlaczego nie obliczyć NUM?
Paolo Tedesco
Zmiana linii sed na: head - $ {X} $ {FILE} | ogon -1 powinien to zrobić
JeffK
4

Linia pojedynczego uderzenia:

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

Nieznaczny problem: zduplikowana nazwa pliku.

asalamon74
źródło
2
mniejszy problem. wykonanie tego na / usr / share / dict / words ma tendencję do faworyzowania słów zaczynających się od „A”. Bawiąc się tym, mam około 90% słów „A” do 10% słów „B”. Żadne zaczynające się od cyfr, które stanowią nagłówek pliku.
śliniaczek
wc -l < test.txtunika konieczności rurek do cut.
fedorqui „SO przestań szkodzić”
3

Oto prosty skrypt w języku Python, który wykona zadanie:

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

Stosowanie:

python randline.py file_to_get_random_line_from
Adam Rosenfield
źródło
1
To nie do końca działa. Zatrzymuje się po jednej linii. Aby to działało, zrobiłem to: import random, sys lines = open(sys.argv[1]).readlines() dla i w zakresie (len (linie)): rand = random.randint (0, len (linie) -1) print lines.pop (rand),
Jed Daniels
Głupi system komentarzy z kiepskim formatowaniem. Czy formatowanie komentarzy kiedyś działało?
Jed Daniels
randint jest włącznie, dlatego len(lines)może prowadzić do IndexError. Możesz użyć print(random.choice(list(open(sys.argv[1])))). Istnieje również efektywny pod względem pamięci algorytm próbkowania zbiornika .
jfs
2
Głodny przestrzeni; rozważ plik 3 TB.
Michael Campbell
@MichaelCampbell: algorytm próbkowania zbiornika , o którym wspomniałem powyżej, może działać z plikiem 3 TB (jeśli rozmiar linii jest ograniczony).
jfs
2

Innym sposobem jest użycie „ awk

awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name
Baskar
źródło
2
Używa awk i bash ( $RANDOMto bashism ). Oto czysta metoda awk (mawk) wykorzystująca tę samą logikę, co cytowany powyżej kod perlfaq5 @ Tracker1: awk 'rand() * NR < 1 { line = $0 } END { print line }' file.name(wow, jest nawet krótszy niż kod perl!)
Adam Katz
Ten kod musi odczytać plik ( wc) w celu uzyskania liczby wierszy, a następnie musi ponownie odczytać (część) pliku ( awk), aby uzyskać zawartość podanego losowego numeru wiersza. We / wy będzie znacznie droższe niż uzyskanie liczby losowej. Mój kod czyta plik tylko raz. Problem z awk rand()polega na tym, że uruchamia się on na podstawie sekund, więc otrzymasz duplikaty, jeśli uruchomisz go zbyt szybko.
Adam Katz,
1

Rozwiązanie, które działa również w systemie MacOSX i powinno również działać w systemie Linux (?):

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

Gdzie:

  • N to liczba losowych linii, którą chcesz

  • NR==FNR {lineN[$1]; next}(FNR in lineN) file1 file2 -> zapisz numery linii zapisane, file1a następnie wydrukuj odpowiednią linięfile2

  • jot -r $N 1 $(wc -l < $file)-> losuj Nliczby losowo ( -r) w zakresie (1, number_of_line_in_file)od jot. Podstawienie procesu <()sprawi, że będzie wyglądać jak plik dla interpretera, tak jak file1w poprzednim przykładzie.
jrjc
źródło
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]}
Rozpoznać
źródło
Ponieważ $ RANDOM generuje liczby mniejsze niż liczba słów w / usr / share / dict / words, która ma 235886 (w każdym razie na moim Macu), po prostu generuję 6 oddzielnych liczb losowych od 0 do 9 i łączę je razem. Następnie upewniam się, że liczba ta jest mniejsza niż 235886. Następnie usuwam zera wiodące, aby zindeksować słowa zapisane w tablicy. Ponieważ każde słowo ma własną linię, można go łatwo użyć w dowolnym pliku do losowego wybrania linii.
Ken
0

Oto, co odkrywam, ponieważ mój system Mac OS nie używa wszystkich łatwych odpowiedzi. Użyłem polecenia jot do wygenerowania liczby, ponieważ zmienne $ RANDOM nie wydają się być zbyt losowe w moim teście. Podczas testowania mojego rozwiązania miałem dużą różnorodność rozwiązań dostarczonych w danych wyjściowych.

  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

Echo zmiennej ma na celu uzyskanie wizualnej wygenerowanej liczby losowej.

dreday13
źródło
0

Używając tylko waniliowej sed i awk oraz bez użycia $ RANDOM, prosty, zajmujący mało miejsca i stosunkowo szybki „jeden wiersz” do wybierania pseudolosowego pojedynczego wiersza z pliku o nazwie FILENAME jest następujący:

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

(Działa to nawet wtedy, gdy FILENAME jest pusta, w którym to przypadku nie jest emitowany żaden wiersz).

Jedną z możliwych zalet tego podejścia jest to, że wywołuje on rand () tylko raz.

Jak wskazał @AdamKatz w komentarzach, inną możliwością byłoby wywołanie rand () dla każdej linii:

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

(Prosty dowód poprawności można podać na podstawie indukcji.)

Zastrzeżenie dotyczące rand()

„W większości implementacji awk, w tym gawk, rand () zaczyna generować liczby z tego samego numeru początkowego lub początkowego za każdym razem, gdy uruchamiasz awk.”

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

szczyt
źródło
Zobacz komentarz opublikowany rok przed tą odpowiedzią , który zawiera prostsze rozwiązanie awk, które nie wymaga sed. Zwróć też uwagę na moje zastrzeżenie dotyczące generatora liczb losowych awk, który wysiewa się przez całe sekundy.
Adam Katz