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!

matowy b
źródło
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 ...

źródło
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
Sklivvz
źródło
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

WCC
źródło
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 .

rozwijać
źródło
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.

itsmatt
źródło
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!

sehe
źródło
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.

Luka Marinko
źródło
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!.).
kenorb
źródło
6

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

head -100 filename | tail -1
Roopa
źródło
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.
Odeyin
źródło
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

pixelbeat
źródło
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>
Ramana Reddy
źródło
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.

Mweerden
źródło
-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
}
Keithel
źródło
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.

osirisgothra
źródło
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>"
Fritz Dodoo
źródło
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
dagelf
źródło
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:]]"; }
węgorz ghEEz
źródło
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,