Powiedzmy, że mam ogromny plik tekstowy (> 2 GB) i chcę tylko cat
wiersze X
do Y
(np. 57890000 do 57890010).
Z tego, co rozumiem, mogę to zrobić przez pipingowanie head
do tail
lub odwrotnie, tj
head -A /path/to/file | tail -B
lub alternatywnie
tail -C /path/to/file | head -D
gdzie A
, B
, C
i D
mogą być obliczane na podstawie liczby linii w pliku, X
a Y
.
Ale z tym podejściem wiążą się dwa problemy:
- Trzeba obliczyć
A
,B
,C
iD
. - Polecenia mogą przesyłać
pipe
sobie o wiele więcej wierszy niż jestem zainteresowany czytaniem (np. Jeśli czytam tylko kilka wierszy w środku dużego pliku)
Czy istnieje sposób, aby powłoka po prostu działała i wyświetlała wiersze, które chcę? (zapewniając tylko X
i Y
)?
tail
cat
large-files
head
Amelio Vazquez-Reina
źródło
źródło
Odpowiedzi:
Proponuję
sed
rozwiązanie, ale ze względu na kompletność,Aby wyciąć po ostatniej linii:
Test prędkości:
seq 100000000 > test.in
real
czas podany przezbash
wbudowanetime
Nie są to w żadnym razie precyzyjne testy porównawcze, ale różnica jest wyraźna i wystarczająco powtarzalna *, aby dać dobre wyobrażenie o względnej prędkości każdego z tych poleceń.
*: Z wyjątkiem pierwszych dwóch
sed -n p;q
ihead|tail
, które wydają się zasadniczo takie same.źródło
tail -n +50000000 test.in | head -n10
, w przeciwieństwie dotail -n-50000000 test.in | head -n10
tego , co dałoby prawidłowy wynik?tail+|head
jest szybszy o 10-15% niż sed, dodałem ten punkt odniesienia.-c
do pominięcia znaków,tail+|head
jest natychmiastowy. Oczywiście nie można powiedzieć „50000000” i może być konieczne ręczne wyszukanie początku szukanej sekcji.Jeśli chcesz wiersze od X do Y włącznie (zaczynając od numeracji od 1), użyj
tail
odczyta i odrzuci pierwsze linie X-1 (nie da się tego obejść), a następnie przeczyta i wydrukuje kolejne linie.head
odczyta i wydrukuje żądaną liczbę wierszy, a następnie wyjdzie. Kiedyhead
wychodzi,tail
odbiera sygnał SIGPIPE i umiera, więc nie będzie czytał więcej niż rozmiar bufora (zwykle kilka kilobajtów) linii z pliku wejściowego.Alternatywnie, jak sugeruje gorkypl , użyj sed:
Rozwiązanie sed jest jednak znacznie wolniejsze (przynajmniej w przypadku narzędzi GNU i Busybox; sed może być bardziej konkurencyjny, jeśli wyodrębnisz dużą część pliku w systemie operacyjnym, w którym przesyłanie jest wolne, a sed szybki). Oto krótkie testy porównawcze pod Linuksem; dane zostały wygenerowane przez
seq 100000000 >/tmp/a
, środowisko to Linux / amd64,/tmp
jest tmpfs, a maszyna jest bezczynna i nie zamienia się.Jeśli znasz zakres bajtów, z którym chcesz pracować, możesz go szybciej wyodrębnić, przechodząc bezpośrednio do pozycji początkowej. Ale w przypadku linii musisz czytać od początku i liczyć nowe wiersze. Aby wyodrębnić bloki od x włącznie do y wyłącznie od 0, przy rozmiarze bloku b:
źródło
tail will read and discard the first X-1 line
wydaje się być unikany, gdy liczba linii jest podawana od końca. W takim przypadku ogon wydaje się czytać wstecz od końca zgodnie z czasem wykonania. Proszę przeczytać:http://unix.stackexchange.com/a/216614/79743
.tail
(w tym GNU tail) mają heurystykę do odczytania od końca. To poprawiatail | head
rozwiązanie w porównaniu do innych metod.head | tail
Podejście jest jednym z najlepszych i najbardziej „idiomatyczne” sposobów, aby to zrobić:Jak zauważył Gilles w komentarzach, szybszy sposób
Jest tak dlatego, że pierwsze linie X - 1 nie muszą przechodzić przez rurę w porównaniu do
head | tail
podejścia.Twoje pytanie w formie wyrażenia jest nieco mylące i prawdopodobnie wyjaśnia niektóre z twoich bezpodstawnych wątpliwości co do tego podejścia.
Mówisz, że trzeba obliczyć
A
,B
,C
,D
ale jak widać, liczba linii pliku nie jest potrzebne, a co najwyżej 1 obliczenie jest konieczne, którego powłoka może zrobić dla ciebie tak czy inaczej.Martwisz się, że rurociągi będą czytały więcej wierszy niż to konieczne. W rzeczywistości nie jest to prawdą:
tail | head
jest tak wydajne, jak można uzyskać pod względem operacji we / wy pliku. Najpierw rozważ minimalną ilość pracy: aby znaleźć X wiersz w pliku, jedynym ogólnym sposobem na to jest odczyt każdego bajtu i zatrzymanie się po policzeniu X symboli nowej linii, ponieważ nie ma sposobu na podzielenie pliku przesunięcie X -tej linii. Po osiągnięciu * X * th linii, trzeba czytać wszystkie linie w celu ich wydrukowania, zatrzymując się na Y tej linii. Zatem żadne podejście nie może uciec czytaniu mniej niż linii Y. Terazhead -n $Y
czyta nie więcej niż Ywiersze (zaokrąglone do najbliższej jednostki bufora, ale bufory, jeśli są używane prawidłowo, poprawiają wydajność, więc nie trzeba się martwić o to narzut). Ponadtotail
nie będzie więcej niż czytaćhead
, dlatego pokazaliśmy, żehead | tail
czyta najmniejszą możliwą liczbę wierszy (ponownie plus nieznaczne buforowanie, które ignorujemy). Jedyną korzyścią wynikającą z podejścia opartego na jednym narzędziu, które nie wykorzystuje rur, jest mniejsza liczba procesów (a tym samym mniejsze koszty ogólne).źródło
Najbardziej ortodoksyjnym sposobem (ale nie najszybszym, jak zauważył Gilles powyżej) byłoby użycie
sed
.W Twoim przypadku:
Ta
-n
opcja oznacza, że tylko odpowiednie linie są drukowane na standardowym wyjściu.Symbol p na końcu numeru linii mety oznacza drukowanie linii w danym zakresie. Q w drugiej części skryptu oszczędza trochę czasu omijając resztę pliku.
źródło
sed
itail | head
będę na równi, ale okazuje się, żetail | head
jest znacznie szybszy (zobacz moją odpowiedź ).tail
/head
są uważane za bardziej „ortodoksyjne”, ponieważ przycinanie dowolnego końca pliku jest dokładnie tym, do czego zostały stworzone. W tych materiałachsed
wydaje się, że wchodzi do obrazu tylko wtedy, gdy wymagane są zmiany - i jest szybko wypychany z obrazu, gdy zaczyna się dziać coś znacznie bardziej złożonego, ponieważ jego składnia dla złożonych zadań jest o wiele gorsza niż AWK, który następnie przejmuje kontrolę .Jeśli znamy zakres do wyboru, od pierwszej linii:
lStart
do ostatniej linii:lEnd
możemy obliczyć:Jeśli znamy całkowitą liczbę wierszy:
lAll
możemy również obliczyć odległość do końca pliku:Wtedy poznamy oba:
Wybór najmniejszego z tych:
tailnumber
jak to:Pozwala nam używać konsekwentnie najszybszego polecenia:
Zwróć uwagę na dodatkowy znak plus („+”), gdy
$linestart
jest zaznaczony.Jedynym zastrzeżeniem jest to, że potrzebujemy całkowitej liczby linii, a znalezienie może zająć trochę czasu.
Jak zwykle w przypadku:
Niektóre mierzone czasy to:
Zauważ, że czasy zmieniają się drastycznie, jeśli wybrane linie znajdują się blisko początku lub końca. Polecenie, które wydaje się działać dobrze po jednej stronie pliku, może być bardzo wolne po drugiej stronie pliku.
źródło
Robię to dość często i dlatego napisałem ten skrypt. Nie muszę znajdować numerów linii, skrypt robi wszystko.
źródło
tail|head
, co zostało obszernie omówione w pytaniu i innych odpowiedziach, a 90% określa numery linii, w których pojawiają się określone ciągi / wzorce, co nie było częścią pytania . PS zawsze powinieneś podawać parametry i zmienne powłoki; np. „3 USD” i „4 USD”.