Szybkie polecenie unix, aby wyświetlić określone wiersze w środku pliku?


206

Próbuję debugować problem z serwerem, a mój jedyny plik dziennika to plik dziennika 20 GB (nawet bez znaczników czasu! Dlaczego ludzie używają System.out.println()do logowania? W produkcji ?!)

Używając grep, znalazłem obszar pliku, który chciałbym obejrzeć, linia 347340107.

Inne niż robienie czegoś takiego

head -<$LINENUM + 10> filename | tail -20 

... które wymagałyby headodczytania pierwszych 347 milionów linii pliku dziennika, czy istnieje szybkie i łatwe polecenie, które zrzuciłoby linie 347340100 - 347340200 (na przykład) na konsolę?

aktualizacja Całkowicie zapomniałem, że grep może wydrukować kontekst wokół dopasowania ... to działa dobrze. Dzięki!


Wyobrażam sobie, że grep musi przeszukać cały plik. Musi istnieć mniej intensywny procesor, aby to zrobić.
ojblass

Odpowiedzi:


69

z GNU-grep można po prostu powiedzieć

grep --context = 10 ...

7
A dokładniej 10 linii przed: grep -B 10 ... Lub 10 linii po: grep -A 10 ...
Boy Baukema

17
To polecenie nie działa, poniżej sed -n '<start>, <end> p' działa
Basav

5
W rzeczywistości nie jest to, co chcesz, ponieważ przetworzy cały plik, nawet jeśli dopasowanie jest w górnym bicie. W tym momencie kombinacja głowa / ogon lub ogon / głowa jest znacznie bardziej skuteczna.
Sklivvz

3
To wcale nie odpowiada zadanemu pytaniu, ponieważ nie oferuje sposobu na wyprowadzenie określonej linii , zgodnie z zadanym pytaniem.
Chris Rasys,

1
Nie o to właściwie pytano. @matt b, dlaczego nie anulujesz tej odpowiedzi?
user1271772

390

Znalazłem dwa inne rozwiązania, jeśli znasz numer linii, ale nic więcej (nie jest możliwe grep):

Zakładając, że potrzebujesz linii od 20 do 40,

sed -n '20,40p;41q' file_name

lub

awk 'FNR>=20 && FNR<=40' file_name

6
+1: Chociaż możesz chcieć wyjść z pracy po wydrukowaniu. Może oferować pewne korzyści w zakresie wydajności, jeśli plik jest naprawdę ogromny.
jaypal singh

awk 'NR> = 20 && NR <= 40' nazwa_pliku
Sudipta Basak

2
sed -n '20, 40p; 41q 'nazwa_pliku dla wyjścia, a następnie.
Snigdha Batra

1
w szczególności są to numery linii początkowej i końcowej. Jeśli znajdujesz się w większym pliku, będzie to „12345678,12345699p”
Code Abominator

1
Oprócz komentarza @ CodeAbominator 41qpoinstruuj sed, aby zrezygnował z linii 41.
Brice

116
# print line number 52
sed -n '52p' # method 1
sed '52!d' # method 2
sed '52q;d' # method 3,  efficient on large files 

metoda 3 wydajna w przypadku dużych plików

najszybszy sposób wyświetlania określonych linii


Próbuję wymyślić, jak dostosować metodę 3, aby używać zakresu zamiast pojedynczej linii, ale obawiam się, że moje sed-foo nie spełnia tego zadania.
Xiong Chiamiov

9
@XiongChiamiov Co powiesz na sed -n '1500p; 501q' do drukowania 1-500?
Sam

3
Powodem, dla którego pierwsze dwie linie / metody są mniej wydajne, jest to, że kontynuują przetwarzanie wszystkich linii po Linii 52, aż do końca, podczas gdy # 3 zatrzymuje się po wydrukowaniu Linii 52.
flow2k

1
Ta odpowiedź skorzystałaby z wyjaśnienia, co robią wszystkie argumenty.
Bram Vanroy

25

Nie, nie ma, pliki nie mogą być adresowane liniowo.

Nie ma żadnego sposobu na znalezienie początku linii n w pliku tekstowym. Musisz przesyłać strumieniowo plik i liczyć nowe wiersze.

Użyj najprostszego / najszybszego narzędzia, które musisz wykonać. Dla mnie używanie headma o wiele większy sens niż grep, ponieważ to drugie jest o wiele bardziej skomplikowane. Nie mówię, że „ grepjest wolny”, tak naprawdę nie jest, ale byłbym zaskoczony, gdyby był szybszy niż headw tym przypadku. headZasadniczo byłby to błąd .


2
O ile wiersze nie mają stałej szerokości w bajtach, nie wiadomo, gdzie przenieść wskaźnik pliku, nie licząc nowych znaków wiersza od początku pliku.
Joseph Lust

To nie daje odpowiedzi na pytanie. Aby skrytykować lub poprosić autora o wyjaśnienia, zostaw komentarz pod postem.
ekshuma

@exhuma Masz rację. Przepisałem. Siedem lat temu denerwowałem się. :)
zrelaksuj się

20

Co powiesz na:

tail -n +347340107 filename | head -n 100

Nie przetestowałem tego, ale myślę, że to zadziała.


Nie, zwykle ogon ma limit 256 ostatnich kilobajtów lub podobny, w zależności od wersji i systemu operacyjnego.
Antti Rytsölä

Ess yessire miller
dctremblay

13

Wolę po prostu wejść do lessi

  • pisanie, 50%aby przejść do połowy pliku,
  • 43210G przejść do linii 43210
  • :43210 zrobić to samo

i podobne rzeczy.

Jeszcze lepiej: naciśnij, vaby rozpocząć edycję (w vimie, oczywiście!), W tym miejscu. Teraz zauważ, że vimma takie same powiązania klawiszy!


12

Najpierw podzieliłem plik na kilka mniejszych, takich jak ten

$ split --lines=50000 /path/to/large/file /path/to/output/file/prefix

a następnie grep na wynikowych plikach.


uzgodniono, przerwij to logowanie i utwórz zadanie crona, aby zrobić to poprawnie. użyj logrotate lub czegoś podobnego, aby nie stać się tak wielkim.
Tanj,

9

Możesz użyć expolecenia, standardowego edytora uniksowego (teraz część Vima), np

  • wyświetl pojedynczą linię (np. drugą linię):

    ex +2p -scq file.txt

    odpowiednia składnia sed: sed -n '2p' file.txt

  • zakres linii (np. 2-5 linii):

    ex +2,5p -scq file.txt

    składnia sed: sed -n '2,5p' file.txt

  • od podanej linii do końca (np. 5 do końca pliku):

    ex +5,p -scq file.txt

    składnia sed: sed -n '2,$p' file.txt

  • wiele zakresów linii (np. 2-4 i 6-8 linii):

    ex +2,4p +6,8p -scq file.txt

    składnia sed: sed -n '2,4p;6,8p' file.txt

Powyższe polecenia można przetestować za pomocą następującego pliku testowego:

seq 1 20 > file.txt

Wyjaśnienie:

  • + lub -c po komendzie - wykonaj komendę (vi / vim) po odczytaniu pliku,
  • -s - tryb cichy, używa również terminala jako domyślnego wyjścia,
  • qpo nim -cnastępuje polecenie wyjścia z edytora (dodaj, !aby wymusić zamknięcie, np -scq!.).

6

Jeśli twój numer linii to 100 do odczytania

head -100 filename | tail -1

6

Dostać ack

Instalacja Ubuntu / Debian:

$ sudo apt-get install ack-grep

Następnie uruchomić:

$ ack --lines=$START-$END filename

Przykład:

$ ack --lines=10-20 filename

Od $ man ack:

--lines=NUM
    Only print line NUM of each file. Multiple lines can be given with multiple --lines options or as a comma separated list (--lines=3,5,7). --lines=4-7 also works. 
    The lines are always output in ascending order, no matter the order given on the command line.

1
To wydaje mi się poleceniem z najbardziej intuicyjną składnią spośród wszystkich odpowiedzi tutaj.
nzn

Od wersji 2.999_06 10 stycznia 2019 r. --linesParametr został usunięty.
burny

4

sed również będzie musiał odczytać dane, aby policzyć linie. Jedynym sposobem, w jaki skrót byłby możliwy, byłby kontekst / kolejność w pliku do działania. Na przykład, jeśli były poprzedzone wiersze dziennika o stałej szerokości daty / godziny itp., Można użyć narzędzia look unix do binarnego przeszukiwania plików dla określonych dat / godzin


4

Posługiwać się

x=`cat -n <file> | grep <match> | awk '{print $1}'`

Tutaj otrzymasz numer linii, w której miało miejsce dopasowanie.

Teraz możesz użyć następującego polecenia, aby wydrukować 100 linii

awk -v var="$x" 'NR>=var && NR<=var+100{print}' <file>

lub możesz również użyć „sed”

sed -n "${x},${x+100}p" <file>

Jeśli masz więcej niż jeden mecz, użyj: „awk” NR == 1 {print $ 1} ”dla pierwszego meczu i tak dalej
Ramana Reddy

2

Ze sed -e '1,N d; M q'będziesz drukować linie N + 1 przez M. Jest to prawdopodobnie nieco lepiej wtedy grep -Cjak nie spróbować dopasować linie wzoru.


-ejest tutaj opcjonalny.
flow2k

2

Opierając się na odpowiedzi Sklivvza, oto fajna funkcja, którą można umieścić w .bash_aliasespliku. Jest skuteczny w przypadku dużych plików podczas drukowania rzeczy z przodu pliku.

function middle()
{
    startidx=$1
    len=$2
    endidx=$(($startidx+$len))
    filename=$3

    awk "FNR>=${startidx} && FNR<=${endidx} { print NR\" \"\$0 }; FNR>${endidx} { print \"END HERE\"; exit }" $filename
}

1

Aby wyświetlić wiersz od <textfile>jego <line#>, po prostu wykonaj następujące czynności:

perl -wne 'print if $. == <line#>' <textfile>

Jeśli chcesz mocniejszego sposobu wyświetlania zakresu wierszy za pomocą wyrażeń regularnych - nie powiem, dlaczego grep jest złym pomysłem na zrobienie tego, powinno być dość oczywiste - to proste wyrażenie pokaże twój zakres w pojedynczy przebieg, który jest tym, czego chcesz, gdy masz do czynienia z plikami tekstowymi ~ 20 GB:

perl -wne 'print if m/<regex1>/ .. m/<regex2>/' <filename>

(wskazówka: jeśli zawiera /w sobie wyrażenie regularne , użyj czegoś takiego jak m!<regex>!)

Zostanie wydrukowane, <filename>zaczynając od pasującej linii, <regex1>aż do linii (i włącznie) pasującej linii <regex2>.

Czarodziej nie musi zobaczyć, jak kilka poprawek może uczynić go jeszcze bardziej wydajnym.

Ostatnia rzecz: perl, ponieważ jest to dojrzały język, ma wiele ukrytych ulepszeń, które sprzyjają szybkości i wydajności. Mając to na uwadze, jest to oczywisty wybór dla takiej operacji, ponieważ pierwotnie została opracowana do obsługi dużych plików dziennika, tekstu, baz danych itp.


tak naprawdę nie wydaje mi się, że w ten sposób, ponieważ kiedy uruchomione jest jedno polecenie perla bardziej skomplikowane niż powiedzmy, uruchamianie ponad 2 programów połączonych razem (dalej w dół strony), i myślę, że tak naprawdę mówisz, ponieważ napisałem więcej wyjaśnienie, które wymagało CZYTANIA, ponieważ na stronie są równie złożone (lub więcej) strony, które nie zostały wydmuchane z wody ... sheesh
osirisgothra

Zauważ, że użytkownik poprosił o szereg linii - twój przykład może być jednak trywialnie dostosowany.
Sklivvz

0

Możesz spróbować tego polecenia:

egrep -n "*" <filename> | egrep "<line number>"

0

Łatwo z perlem! Jeśli chcesz pobrać wiersz 1, 3 i 5 z pliku, powiedz / etc / passwd:

perl -e 'while(<>){if(++$l~~[1,3,5]){print}}' < /etc/passwd

1
Mówisz, że z awk jest to łatwe, ale zamiast tego zrobiłeś to w perlu?
Więzień 13

0

Dziwię się, że tylko jedna inna odpowiedź (autorstwa Ramany Reddy) zasugerowała dodanie numerów wierszy do wyniku. Poniżej szuka wymaganego numeru linii i koloruje wydruk.

file=FILE
lineno=LINENO
wb="107"; bf="30;1"; rb="101"; yb="103"
cat -n ${file} | { GREP_COLORS="se=${wb};${bf}:cx=${wb};${bf}:ms=${rb};${bf}:sl=${yb};${bf}" grep --color -C 10 "^[[:space:]]\\+${lineno}[[:space:]]"; }

Odpowiedzi zawierające kod mają tendencję do oznaczania do usunięcia. Czy mógłbyś dodać komentarz na temat tego, jak to rozwiązuje problem?
Graham,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.