Jaki jest łatwy sposób na odczytanie losowej linii z pliku w linii poleceń Uniksa?
Jaki jest łatwy sposób na odczytanie losowej linii z pliku w linii poleceń Uniksa?
Odpowiedzi:
Możesz użyć shuf
:
shuf -n 1 $FILE
Istnieje również narzędzie o nazwie rl
. W Debianie jest w randomize-lines
pakiecie, który robi dokładnie to, co chcesz, choć nie jest dostępny we wszystkich dystrybucjach. Na swojej stronie głównej zaleca używanie shuf
zamiast niej (jak sądzę, która nie istniała podczas jej tworzenia). shuf
jest częścią jądra GNU, rl
nie jest.
rl -c 1 $FILE
shuf
wskazówkę, jest wbudowany w Fedorę.
sort -R
pewnością sprawi, że będziesz musiał dużo czekać, jeśli poradzisz sobie z bardzo dużymi plikami - 80kk linii - podczas gdy shuf -n
działa dość natychmiastowo.
coreutils
z Homebrew. Może być wywołany gshuf
zamiast shuf
.
randomize-lines
w systemie OS X przezbrew install randomize-lines; rl -c 1 $FILE
shuf
jest 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).
Inna alternatywa:
head -$((${RANDOM} % `wc -l < file` + 1)) file | tail -1
(${RANDOM} << 15) + ${RANDOM}
. To znacznie zmniejsza obciążenie i pozwala na pracę z plikami zawierającymi do 1 miliarda linii.
+
i |
są takie same, ponieważ ${RANDOM}
z definicji ma wartość 0..32767.
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ę)
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
sort -R <<< $'1\n1\n2' | head -1
prawdopodobnie zwróci 1 i 2, ponieważ sort -R
sortuje zduplikowane linie razem. To samo dotyczy sort -Ru
, ponieważ usuwa duplikaty linii.
sort
przed przesłaniem go do potoku head
. shuf
zamiast tego wybiera losowe linie z pliku i jest dla mnie znacznie szybszy.
sort --random-sort $FILE | head
byłoby najlepiej, ponieważ pozwala on na bezpośredni dostęp do pliku, prawdopodobnie umożliwiając wydajne sortowanie równoległe
--random-sort
I -R
opcje 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).
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”.
-n 1
określa 1 linię, i możesz zmienić ją na więcej niż 1. shuf
Można jej również użyć do innych rzeczy; Właśnie wykonałem potok ps aux
i grep
za jego pomocą losowo zabiłem procesy częściowo pasujące do nazwy.
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.
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ą).
shuf
przechowuje 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.
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}
Linia pojedynczego uderzenia:
sed -n $((1+$RANDOM%`wc -l test.txt | cut -f 1 -d ' '`))p test.txt
Nieznaczny problem: zduplikowana nazwa pliku.
wc -l < test.txt
unika konieczności rurek do cut
.
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
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),
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 .
Innym sposobem jest użycie „ awk ”
awk NR==$((${RANDOM} % `wc -l < file.name` + 1)) file.name
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.
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, file1
a następnie wydrukuj odpowiednią linięfile2
jot -r $N 1 $(wc -l < $file)
-> losuj N
liczby 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 file1
w poprzednim przykładzie.#!/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]}
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.
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.)
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