2012-04-12 12 views
9

Mam plik tekstowy z nieznaną liczbą wierszy. Muszę pobrać losowo niektóre z tych linii, ale nie chcę żadnego ryzyka powtórzeń.Jak wybrać losowe unikalne linie z pliku tekstowego w powłoce?

Próbowałem to:

jot -r 3 1 `wc -l<input.txt` | while read n; do 
    awk -v n=$n 'NR==n' input.txt 
done 

Ale to jest brzydkie i nie chronią przed powtórzeniami.

Próbowałem też tak:

awk -vmax=3 'rand() > 0.5 {print;count++} count>max {exit}' input.txt 

Ale to oczywiście nie jest to najlepsze rozwiązanie albo, jak ja nie gwarantuje nawet dostać max linie.

utknąłem. Jak mam to zrobic?

Odpowiedz

2

Jeśli jot jest w systemie, wtedy myślę używasz FreeBSD lub OSX zamiast Linux, więc prawdopodobnie nie mają narzędzi, takich jak rl lub sort -R dostępne.

Bez obaw. Musiałem to zrobić jakiś czas temu. Spróbuj zamiast tego:

[[email protected] ~]$ cat rndlines 
#!/bin/sh 

# default to 3 lines of output 
lines="${1:-3}" 

# First, put a random number at the begginning of each line. 
while read line; do 
    echo "`jot -r 1 1 1000000` $line" 
done <input.txt> stage1.txt 

# Next, sort by the random number. 
sort -n stage1.txt > stage2.txt 

# Last, remove the number from the start of each line. 
sed -r 's/^[0-9]+ //' stage2.txt > stage3.txt 

# Show our output 
head -n "$lines" stage3.txt 

# Clean up 
rm stage1.txt stage2.txt stage3.txt 

[[email protected] ~]$ ./rndlines input.txt 
two 
one 
five 
[[email protected] ~]$ ./rndlines input.txt 
four 
two 
three 
[[email protected] ~]$ 

Mój input.txt ma pięć linii o nazwanych numerach.

Wypisałem to dla łatwiejszego czytania, ale w rzeczywistości można łączyć rzeczy w długie rury, a będziesz chciał wyczyścić dowolne (jednoznacznie nazwane) pliki tymczasowe, które możesz utworzyć.

Oto przykład 1-linia, która także wstawia liczbę losową trochę bardziej czysto używając awk:

$ printf 'one\ntwo\nthree\nfour\nfive\n' | awk 'BEGIN{srand()} {printf("%.20f %s\n", rand(), $0)}' | sort | head -n 3 | cut -d\ -f2- 

Należy pamiętać, że starsze wersje sed (w FreeBSD i OSX) może wymagać opcję -E zamiast -r do obsługi ERE zamiast lub dialektu BRE w wyrażeniu regularnym. (Oczywiście, możesz wyrazić to w BRE, ale dlaczego?) (Starożytne wersje sed (HP/UX, itp.) Mogą wymagać BRE, ale używałbyś ich tylko, gdybyś już wiedział, jak to zrobić.)

+1

To wygląda jak to będzie pracować dla mnie, bez potrzeby instalowania żadnych dodatkowych narzędzi lub języki (perl, python, bash). Masz rację, używam FreeBSD. Dzięki za spisanie wszystkiego. Połączę te polecenia z rurkami, aby były bardziej zwarte. – Graham

+0

cat/path/to/file | awk 'BEGIN {srand()} {print rand() "\ t" 0 0} "| sort -n | cut -f2-> /path/to/random.file – CodeReaper

+0

@CodeReaper - ya, używając awk i cut make rzeczy czystsze. Połamałem kroki w oddzielnych liniach, aby ułatwić dokumentację. – ghoti

3

Jeśli masz Python dostępne (zmienić 10 co chcesz):

python -c 'import random, sys; print("".join(random.sample(sys.stdin.readlines(), 10)).rstrip("\n"))' < input.txt 

(to będzie działać w Pythonie 2.x oraz 3.x)

również (ponownie zmienić 10 do odpowiedniej wartości):

sort -R input.txt | head -10 
2

To powinno wystarczyć, przynajmniej z bash i zakładając, że środowisko ma innych poleceń dostępnych:

cat chk.c | while read x; do 
    echo $RANDOM:$x 
done | sort -t: -k1 -n | tail -10 | sed 's/^[0-9]*://' 

Zasadniczo Wyjścia plik, umieszczając liczbę losową na początku każdej linii.

Następnie sortuje na ten numer, chwyta 10 ostatnich linii i usuwa z nich ten numer.

Dlatego daje dziesięć losowych linii z pliku, bez powtórzeń.

Na przykład, oto zapis nim uruchomiony trzy razy z tego pliku chk.c:

==== 
pax$ testprog chk.c 
} else { 
} 
newNode->next = NULL; 
colm++; 

==== 
pax$ testprog chk.c 
} 

arg++; 
printf (" [%s] n", currNode->value); 
free (tempNode->value); 

==== 
pax$ testprog chk.c 

char tagBuff[101]; 
} 
return ERR_OTHER; 
#define ERR_MEM 1 

=== 
pax$ _ 
+0

Nie mam zainstalowanego bash, po prostu sh. Spróbuję zainstalować, jeśli inne rozwiązania nie działają. Dzięki. – Graham

+0

To jest bardziej prawdopodobne, że wybierzesz linie od końca plików, ponieważ może istnieć więcej niż n linii zaczynających się od 32767. – user495470

+0

@Lri, nie, nie będzie. Losowe liczby są przypisywane (pseudo) losowo. Nie jest bardziej prawdopodobne, że linie będą miały 32767 niż jakikolwiek inny numer. Szybkość będzie problemem tylko wtedy, gdy pliki będą masywne, w tym przypadku prawdopodobnie nie będę nawet używać powłoki. – paxdiablo

4

To może pracować dla Ciebie:

shuf -n3 file 

shuf jest jednym z coreutils GNU.

+1

Niestety, GNU coreutils nie są wbudowane w FreeBSD. – ghoti

+0

@ghoti Nie tylko OP nie wspomniał o FreeBSD, ale także nie wspomniał o tym, że jest przeciwny instalowaniu nowego oprogramowania. W systemie OS X są one łatwo dostępne jako "brew install coreutils". – Johann

+1

@Johann W rzeczywistości, OP * tak * w rzeczywistości wspomniał, że używa FreeBSD w wielu komentarzach (w mojej odpowiedzi i na temat Glenna), a jego pytanie wspomniało o narzędziach, które pochodzą z FreeBSD. – ghoti

1

Aby uzyskać N losowej linii z FILE z Perl:

perl -MList::Util=shuffle -e 'print shuffle <>' FILE | head -N 
+0

Perl może wykonać zadanie 'head':' perl -MList :: Util = shuffle -e '$ n = 3; @foo = shuffle <>; print @ foo [0 .. $ n] 'FILE' –

+0

Oczywiście, ale uważam, że jest wygodniejszy. Nie mam nic przeciwko miksowaniu różnych narzędzi, czasami używam filtrów z narzędziami awk/sed/perl i unix w jednym poleceniu. Raz nawet użyłem pytona :). – yazu

2
sort -Ru filename | head -5 

zapewni żadnych duplikatów. Nie wszystkie implementacje sort mają opcję -R.

+0

Jestem w FreeBSD, jak wskazał Ghoti. Dzięki i tak. – Graham

+0

'sort -Ru <<< $ '1 \ n1 \ n2' | head -2' usuwa duplikaty wierszy, więc nigdy nie zwraca 1 i 1. Bez '-u' duplikaty wierszy zostałyby posortowane razem, więc powróciłyby one 1 i 1 lub 2 i 1. – user495470

1

Oto odpowiedź za pomocą rubin, jeśli nie chcesz instalować niczego innego:

cat filename | ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")' 

Przykładowo, jeżeli plik (dups.txt), która wygląda następująco:

1 2 
1 3 
2 
1 2 
3 
4 
1 3 
5 
6 
6 
7 

Ty może uzyskać następujące dane wyjściowe (lub jakiś permutacji):

cat dups.txt| ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")' 
4 
6 
5 
1 2 
2 
3 
7 
1 3 

Kolejny przykład z komentarzy:

printf 'test\ntest1\ntest2\n' | ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")' 
test1 
test 
test2 

Oczywiście jeśli masz plik z powtarzających się linii testu dostaniesz tylko jedną linię:

printf 'test\ntest\ntest\n' | ruby -e 'puts ARGF.read.split("\n").uniq.shuffle.join("\n")' 
test 
+0

Jak to losuje wyjście? Ponadto, jeśli ta sama zawartość znajduje się w wielu wierszach pliku wejściowego, dane wyjściowe powinny pokazywać dane dwukrotnie, ponieważ chcę wybrać unikalne * linie *, a nie unikalne * treści *. – Graham

+0

Przepraszam, że nieumyślnie pominąłem shuffle. Ten uniq działa tylko na tablicy linii, więc powinieneś otrzymywać unikalne linie, które nie są treścią. – rainkinz

+0

Dzięki, ale myślę, że 'uniq' nie zachowuje się w ten sposób. Jeśli karmię Twój skrypt ruby ​​wyjściem 'printf 'test \ ntest \ ntest \ n'', to powinienem zobaczyć trzy linie' testu' jako moje wyjście. Widzę tylko jeden. Według [ruby doc] (http://ruby-doc.org/core-2.0.0/Array.html#method-i-uniq) 'uniq' zwraca unikalne * wartości *, co nie jest tym, czego chcę przetwarzać moje dane wejściowe. – Graham

Powiązane problemy