Polecenie, aby wyświetlić kilka pierwszych i ostatnich kilku wierszy pliku


23

Mam plik z wieloma wierszami, a każdy wiersz ma na początku znacznik czasu, na przykład

[Thread-3] (21/09/12 06:17:38:672) logged message from code.....

Dlatego często sprawdzam 2 rzeczy z tego pliku dziennika.

  1. Podano również kilka pierwszych wierszy, które mają warunki globalne i czas rozpoczęcia.
  2. Ostatnie kilka wierszy, które mają status wyjścia z kilkoma innymi informacjami.

Czy jest jakieś szybkie, wygodne, pojedyncze polecenie, które pozwala mi wyświetlić tylko kilka pierwszych wierszy pliku?


2
Jakie są warunki globalne i nie head and taildziała dla Ciebie?
stokrotka,

To jest część mojego pliku dziennika. Starałem się rozwodzić. Możesz to zignorować.
mtk

Twoje rozwiązanie wygląda dla mnie dobrze. Jeśli chcesz uzyskać większą wygodę, przekształć go w funkcję powłoki (może to zrobić nawet alias).
vonbrand

@vonbrand Problem polega na tym, że nie wiemN
Bernhard

@Bernhard, nie jestem sed(1)ekspertem, ale istnieją sposoby na schowanie rzeczy do późniejszego użytku. Może warto się tam zajrzeć. OTOH, prawdopodobnie użyłbym skryptu Perla (lub cokolwiek), aby to zrobić, jeśli jest często używany, ponieważ jestem bardziej zaznajomiony z tym.
vonbrand

Odpowiedzi:


12

Możesz użyć sedlub, awkaby zrobić to za pomocą jednego polecenia. Jednak stracisz szybkość, ponieważ sedi tak awkbędzie musiał przejść przez cały plik. Z punktu widzenia prędkości znacznie lepiej jest wykonać funkcję lub za każdym razem kombinację tail+ head. Ma to tę wadę, że nie działa, jeśli dane wejściowe są potokiem, jednak można użyć podstawienia proccess, na wypadek, gdyby powłoka je obsługiwała (patrz przykład poniżej).

first_last () {
    head -n 10 -- "$1"
    tail -n 10 -- "$1"
}

i po prostu uruchom go jako

first_last "/path/to/file_to_process"

aby kontynuować proces podstawiania (tylko powłoki bash, zsh, ksh jak powłoki):

first_last <( command )

ps. możesz nawet dodać znak, grepaby sprawdzić, czy istnieją twoje „warunki globalne”.


-n 10jest domyślne, nie?
l0b0

@ l0b0 tak, to domyślnie. -n 10nie jest tu konieczne.
pędzi

20

@ rush ma rację, mówiąc, że użycie głowy i ogona jest bardziej wydajne w przypadku dużych plików, ale w przypadku małych plików (<20 linii) niektóre linie mogą być wyprowadzane dwukrotnie.

{ head; tail;} < /path/to/file

byłby równie wydajny, ale nie miałby powyższego problemu.


W przeciwieństwie do rozwiązania typu rush, nie działa to w powłoce POSIX.
Marco

2
@Marco Huh? Używane są tu tylko konstrukcje POSIX. Co widzisz, że idzie źle?
Gilles „SO- przestań być zły”

2
@Gilles Brakowało mi miejsca: {head; tail;} < filedziała w Zsh, ale nie działa w SH. { head; tail;} < filezawsze działa. Przepraszam za hałas.
Marco

@Marco, gdyby były z tym problemy, byłoby to z headpowłoką, a nie z powłoką. POSIX wymaga headpozostawienia kursora w pliku tuż obok tych 10 wierszy dla zwykłych plików. Problem może pojawić się w przypadku headimplementacji innych niż POSIX (bardzo stare wersje GNU head były w tym przypadku niezgodne, ale mówimy dekady) lub jeśli plik nie jest widoczny (np. Nazwany potok lub gniazdo, ale wtedy inne rozwiązanie miałoby ten sam problem).
Stéphane Chazelas

1
@FCTW,sudo sh -c '{ head; tail;} < /path/to/file'
Stéphane Chazelas,

9

{ head; tail; }Rozwiązanie nie będzie działać na rurach (lub gniazd lub jakichkolwiek innych plików niż możliwy do przeszukania), ponieważ headmogłyby zużywają zbyt dużo danych, jak to brzmi w blokach i nie może żądać z powrotem na rurze potencjalnie pozostawiając kursor wewnątrz pliku poza to, co tailjest przeznaczona wybrać.

Możesz więc użyć narzędzia, które odczytuje jeden znak naraz, takiego jak powłoka read(tutaj, używając funkcji, która przyjmuje liczbę linii nagłówka i linii ogona jako argumenty).

head_tail() {
  n=0
  while [ "$n" -lt "$1" ]; do
    IFS= read -r line || { printf %s "$line"; break; }
    printf '%s\n' "$line"
    n=$(($n + 1))
  done
  tail -n "${2-$1}"
}
seq 100 | head_tail 5 10
seq 20 | head_tail 5

lub zaimplementuj tailw awk na przykład jako:

head_tail() {
  awk -v h="$1" -v t="${2-$1}" '
    {l[NR%t]=$0}
    NR<=h
    END{
      n=NR-t+1
      if(n <= h) n = h+1
      for (;n<=NR;n++) print l[n%t]
    }'
}

Z sed:

head_tail() {
  sed -e "1,${1}b" -e :1 -e "$(($1+${2-$1})),\$!{N;b1" -e '}' -e 'N;D'
}

(choć należy pamiętać, że niektóre sedimplementacje mają niskie ograniczenie wielkości obszaru wzorców, więc zawiodłyby w przypadku dużych wartości liczby linii końcowych).


4

Korzystając z bashpodstawiania procesów, możesz wykonać następujące czynności:

make_some_output | tee >(tail -n 2) >(head -n 2; cat >/dev/null) >/dev/null

Zauważ, że linie nie są w porządku, chociaż w przypadku plików dłuższych niż około 8kB, prawdopodobnie będą. Ta wartość graniczna 8 kB jest typowym rozmiarem bufora odczytu i jest związana z przyczyną, dla | {head; tail;}której nie działa dla małych plików.

Jest cat >/dev/nullto konieczne, aby utrzymać headrurociąg przy życiu. W przeciwnym razie teewyjdzie wcześniej, a gdy otrzymasz dane wyjściowe tail, będzie ono pochodziło gdzieś pośrodku wejścia, a nie na końcu.

Wreszcie, dlaczego >/dev/nullzamiast, powiedzmy, przejść taildo innego |? W następującym przypadku:

make_some_output | tee >(head -n 2; cat >/dev/null) | tail -n 2  # doesn't work

headStandardowe wejście jest podawane do potoku tailzamiast do konsoli, co wcale nie jest tym, czego chcemy.


Kiedy głowa lub ogon skończą pisać wyjście, które chcą, zamykają standardowe wejście i wychodzą. Stąd pochodzi SIGPIPE. Zwykle jest to dobra rzecz, ponieważ odrzucają resztę danych wyjściowych, więc nie ma powodu, aby druga strona potoku kontynuowała spędzanie czasu na ich generowaniu.
derobert

Co sprawia, że ​​zamówienie zostanie utrzymane? Prawdopodobnie będzie to dla dużego pliku, ponieważ tailmusi działać dłużej, ale spodziewam się (i widzę), że kończy się to mniej więcej o połowę krótszym czasem.
Gilles „SO- przestań być zły”

Dostaniesz SIGPIPE z tee >(head) >(tail)tych samych powodów ( >(...)które, nawiasem mówiąc, jest teraz funkcją ksh obsługiwaną teraz przez zarówno zsh jak i bash), używa również potoków. Możesz to zrobić, ... | (trap '' PIPE; tee >(head) >(tail) > /dev/null)ale nadal będą wyświetlane komunikaty o błędach uszkodzonych rurtee .
Stéphane Chazelas

W moim systemie (bash 4.2.37, coreutils 8.13) ten, tailktóry został zabity przez SIGPIPE, nie jest teei tailnie pisze do potoku. Więc musi to być kill()prawda? A dzieje się tak tylko wtedy, gdy używam |składni. stracemówi, że teeto nie dzwonienie kill()... więc może bash?
Jander

1
@Jander, spróbuj nakarmić więcej niż 8 seq 100000 | tee >(head -n1) >(tail -n1) > /dev/null
tysięcy

3

Za pomocą ed(który wczyta cały plik do pamięci RAM):

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' 'H' '1,10p' '$-10,$p' 'q' | ed -s file

Krótszy:ed -s file <<< $'11,$-10d\n,p\nq\n'
don_crissti,

2

Pierwsze rozwiązanie Stephane'a w funkcji, dzięki czemu można używać argumentów (działa w dowolnej powłoce Bourne'a lub POSIX):

head_tail() {
    head "$@";
    tail "$@";
}

Teraz możesz to zrobić:

head_tail -n 5 < /path/to/file

To oczywiście zakłada, że ​​patrzysz tylko na jeden plik i że rozwiązanie Stephane działa (niezawodnie) tylko na zwykłych (możliwych do przeglądania) plikach.


2

Dzięki opcji -u( --unbuffered) GNU sedmożesz użyć sed -u 2qjako niebuforowanej alternatywy dla head -n2:

$ seq 100|(sed -u 2q;tail -n2)
1
2
99
100

(head -n2;tail -n2)kończy się niepowodzeniem, gdy ostatnie wiersze są częścią bloku danych wejściowych zużywanych przez head:

$ seq 1000|(head -n2;tail -n2)
1
2
999
1000
$ seq 100|(head -n2;tail -n2)
1
2

to powinna być najlepsza odpowiedź! działa jak marzenie!
Ben Usman,

1

Wpadłem dziś na coś takiego, gdzie potrzebowałem tylko ostatniej linii i kilku linii z przodu strumienia i wymyśliłem następujące.

sed -n -e '1{h}' -e '2,3{H}' -e '${H;x;p}'

Czytam to w następujący sposób: zainicjuj przestrzeń wstrzymania zawartością pierwszego wiersza, dodaj linie 2-3 w przestrzeni wstrzymania, w EOF dołącz ostatnią linię do przestrzeni wstrzymania, zamień przestrzeń wstrzymania i wzorca i wydrukuj wzór przestrzeń.

Być może ktoś, kto ma więcej sedniż -fu niż ja, może wymyślić, jak to uogólnić, aby wydrukować kilka ostatnich wierszy strumienia wskazanego w tym pytaniu, ale nie potrzebowałem tego i nie mogłem znaleźć łatwego sposobu na matematykę na podstawie $adresu w sedlub być może przez zarządzanie przestrzenią wstrzymania, aby po osiągnięciu pozostało tylko kilka ostatnich linii EOF.


1

Możesz wypróbować Perla, jeśli go masz:

perl -e '@_ = <>; @_=@_[0, -3..-1]; print @_'

Działa to w przypadku większości plików, ale odczytuje cały plik do pamięci przed jego przetworzeniem. Jeśli nie znasz plasterków Perla, „0” w nawiasach kwadratowych oznacza „weź pierwszą linię”, a „-3 ...- 1” oznacza „weź trzy ostatnie linie”. Oba możesz dostosować do swoich potrzeb. Jeśli potrzebujesz przetwarzać naprawdę duże pliki (co jest „duże” może zależeć od twojej pamięci RAM i być może rozmiarów wymiany), możesz wybrać:

perl -e 'while($_=<>){@_=(@_,$_)[0,-3..-1]}; print @_'

może być nieco wolniejszy, ponieważ tworzy plasterek przy każdej iteracji, ale jest niezależny od rozmiaru pliku.

Oba polecenia powinny działać zarówno w potokach, jak i ze zwykłymi plikami.

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.