unix - nagłówek AND koniec pliku

133

Powiedzmy, że masz plik txt, jakie jest polecenie, aby wyświetlić 10 pierwszych wierszy i 10 ostatnich wierszy pliku jednocześnie?

tzn. jeśli plik ma 200 linii, wyświetlaj wiersze 1-10 i 190-200 za jednym razem.

toop
źródło
Co masz na myśli mówiąc „za jednym zamachem”?
cnicutar
@cnicutar ie. nie idę plik head -10 patrząc na dane, a potem osobno przechodząc plik tail -10 i patrząc na dane
koniec
@toop Jeśli chcesz uzyskać prawdziwy działający przykład, zobacz stackoverflow.com/a/44849814/99834
sorin

Odpowiedzi:

212

Możesz po prostu:

(head; tail) < file.txt

A jeśli z jakiegoś powodu potrzebujesz rur, to w ten sposób:

cat file.txt | (head; tail)

Uwaga: wydrukuje zduplikowane linie, jeśli liczba linii w pliku.txt jest mniejsza niż domyślne linie nagłówka + domyślne linie końca.

Aleksandra Zalcman
źródło
54
Ściśle mówiąc, nie daje to ogona oryginalnego pliku, ale koniec strumienia po headzużyciu pierwszych 10 wierszy pliku. (Porównaj to z head < file.txt; tail < file.txtplikiem zawierającym mniej niż 20 linii). Tylko bardzo drobna kwestia, o której należy pamiętać. (Ale wciąż +1.)
chepner
15
Ładny. Jeśli chcesz mieć przerwę między częściami głowy i ogona: (głowa; echo; ogon) <file.txt
Simon Hibbs
3
Ciekawe, dlaczego / jak to działa.
Zadano
9
@nametal Właściwie możesz nawet nie dostać tego dużo. Chociaż wyświetlahead tylko pierwsze 10 wierszy swojego wejścia, nie ma gwarancji, że nie zużywa go więcej w celu znalezienia końca dziesiątego wiersza, pozostawiając mniej wejścia do wyświetlenia. less
chepner
20
Przykro mi to mówić, ale odpowiedź działa tylko w niektórych przypadkach. seq 100 | (head; tail)daje mi tylko pierwsze 10 liczb. Tylko przy znacznie większym rozmiarze wejściowym (takim jak seq 2000) ogon otrzymuje pewne dane wejściowe.
modułowe
18

ed jest standard text editor

$ echo -e '1+10,$-10d\n%p' | ed -s file.txt
kev
źródło
2
Co się stanie, jeśli plik ma więcej lub mniej niż 200 wierszy? Nie znasz liczby linii ab initio?
Paul
@Paul Zmieniłem się sednaed
kev
15

W przypadku czystego strumienia (np. Wyjście z polecenia), możesz użyć „tee”, aby rozwidlić strumień i wysłać jeden strumień do głowy i jeden do końca. Wymaga to użycia funkcji '> (list)' w bash (+ / dev / fd / N):

( COMMAND | tee /dev/fd/3 | head ) 3> >( tail )

lub używając / dev / fd / N (lub / dev / stderr) plus podpowłoki ze skomplikowanym przekierowaniem:

( ( seq 1 100 | tee /dev/fd/2 | head 1>&3 ) 2>&1 | tail ) 3>&1
( ( seq 1 100 | tee /dev/stderr | head 1>&3 ) 2>&1 | tail ) 3>&1

(Żadne z nich nie będzie działać w csh ani tcsh).

Aby uzyskać trochę lepszą kontrolę, możesz użyć tego polecenia perla:

COMMAND | perl -e 'my $size = 10; my @buf = (); while (<>) { print if $. <= $size; push(@buf, $_); if ( @buf > $size ) { shift(@buf); } } print "------\n"; print @buf;'
RantingNerd
źródło
1
+1 za obsługę strumienia. Możesz ponownie użyć stderr:COMMAND | { tee >(head >&2) | tail; } |& other_commands
jfs
2
btw, psuje się dla plików większych niż rozmiar bufora (8K w moim systemie). cat >/dev/nullnaprawia to:COMMAND | { tee >(head >&2; cat >/dev/null) | tail; } |& other_commands
jfs
Spodobało mi się to rozwiązanie, ale po kilku chwilach grania zauważyłem, że w niektórych przypadkach ogon biegał przed głową ... nie ma gwarantowanej kolejności między komendami headi tail: \ ...
Jan
8
(sed -u 10q; echo ...; tail) < file.txt

Kolejna odmiana (head;tail)motywu, ale unikająca początkowego problemu z wypełnianiem bufora w przypadku małych plików.

Gość
źródło
4

head -10 file.txt; tail -10 file.txt

Poza tym będziesz musiał napisać własny program / skrypt.

mah
źródło
1
Fajnie, zawsze używałem cati / headlub tailrurami, dobrze wiedzieć, że mogę ich używać indywidualnie!
Paul
Jak mogę następnie przesłać te pierwsze 10 + ostatnie 10 do innego polecenia?
toop
1
@Paul - z 'your_program' jako wc -l zwraca 10 zamiast 20
toop
3
lub bez konieczności odradzania podpowłoki: { head file; tail file; } | prog(wymagane są odstępy wewnątrz nawiasów klamrowych i końcowy średnik)
glenn jackman
1
Wow ... głos negatywny za otrzymanie odpowiedzi dość podobnej do innych (ale z sygnaturą czasową przed nimi) po prawie dwóch latach, od kogoś, kto zdecydował się nie publikować, dlaczego głosował w dół. Ładny!
mah
4

Na podstawie komentarza JF Sebastiana :

cat file | { tee >(head >&3; cat >/dev/null) | tail; } 3>&1

W ten sposób możesz przetwarzać pierwszą linię i resztę inaczej w jednej rurze, co jest przydatne podczas pracy z danymi CSV:

{ echo N; seq 3;} | { tee >(head -n1 | sed 's/$/*2/' >&3; cat >/dev/null) | tail -n+2 | awk '{print $1*2}'; } 3>&1
N * 2
2
4
6
modułowy
źródło
3

Problem polega na tym, że programy zorientowane strumieniowo nie znają z wyprzedzeniem długości pliku (ponieważ może go nie być, jeśli jest to prawdziwy strumień).

narzędzia takie jak tailbuforowanie ostatnich n widzianych wierszy i czekanie na koniec strumienia, a następnie drukowanie.

jeśli chcesz to zrobić za pomocą jednego polecenia (i chcesz, aby działało z dowolnym przesunięciem i nie powtarzaj wierszy, jeśli się nakładają), będziesz musiał emulować to zachowanie, o którym wspomniałem.

spróbuj tego awk:

awk -v offset=10 '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' yourfile
Samus_
źródło
wymaga więcej pracy, aby uniknąć problemów, gdy przesunięcie jest większe niż plik
Samus_
Tak, to działa z wyjściem a.out | awk -v ...
potokowym
rzeczywiście :) ale to normalne zachowanie awk, większość programów uruchamianych z wiersza poleceń działa na stdin, gdy są wywoływane bez argumentów.
Samus_
1
Bardzo blisko pożądanego zachowania, ale wydaje się, że dla <10 linii dodaje dodatkowe nowe linie.
sorin
3

Skończyło się na tym rozwiązaniu, które wydaje się być jedynym, które obejmowało wszystkie przypadki użycia (do tej pory):

command | tee full.log | stdbuf -i0 -o0 -e0 awk -v offset=${MAX_LINES:-200} \
          '{
               if (NR <= offset) print;
               else {
                   a[NR] = $0;
                   delete a[NR-offset];
                   printf "." > "/dev/stderr"
                   }
           }
           END {
             print "" > "/dev/stderr";
             for(i=NR-offset+1 > offset ? NR-offset+1: offset+1 ;i<=NR;i++)
             { print a[i]}
           }'

Lista funkcji:

  • wyjście na żywo dla głowy (oczywiście, że dla ogona nie jest możliwe)
  • brak korzystania z plików zewnętrznych
  • pasek postępu jedna kropka dla każdej linii po MAX_LINES, bardzo przydatne w przypadku długotrwałych zadań.
  • progressbar na stderr, zapewniający, że kropki postępu są oddzielone od głowy + ogona (bardzo przydatne, jeśli chcesz wyprowadzić stdout)
  • unika możliwej nieprawidłowej kolejności logowania z powodu buforowania (stdbuf)
  • unikaj duplikowania danych wyjściowych, gdy całkowita liczba wierszy jest mniejsza niż nagłówek + koniec.
sorin
źródło
2

Od jakiegoś czasu szukałem tego rozwiązania. Wypróbowałem to sam z sedem, ale problem braku znajomości długości pliku / strumienia był nie do pokonania. Ze wszystkich opcji dostępnych powyżej podoba mi się rozwiązanie awk Camille Goudeseune. Zanotował, że jego rozwiązanie pozostawiło dodatkowe puste wiersze na wyjściu z wystarczająco małym zestawem danych. Tutaj podaję modyfikację jego rozwiązania, która usuwa dodatkowe linie.

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { a_count=0; for (i in a) {a_count++}; for (i=NR-a_count+1; i<=NR; i++) print a[i] }' ; }
Michael Blahay
źródło
1

Cóż, zawsze możesz je połączyć. Tak jak tak head fiename_foo && tail filename_foo. Jeśli to nie wystarczy, możesz napisać sobie funkcję bash w swoim pliku .profile lub dowolnym pliku logowania, którego używasz:

head_and_tail() {
    head $1 && tail $1
}

A później powołać go z powłoki wiersza: head_and_tail filename_foo.

SRI
źródło
1

Pierwsze 10 wierszy pliku file.ext, a następnie jego ostatnie 10 wierszy:

cat file.ext | head -10 && cat file.ext | tail -10

Ostatnie 10 wierszy pliku, a następnie pierwsze 10:

cat file.ext | tail -10 && cat file.ext | head -10

Następnie możesz potokować wyjście również w innym miejscu:

(cat file.ext | head -10 && cat file.ext | tail -10 ) | your_program

Paweł
źródło
5
Po co używać cat, skoro możesz po prostu zadzwonić do pliku head -10 file.txt?
jstarek
Czy możesz zmienić liczbę wierszy, aby wywołanie wyglądało mniej więcej tak: head_ tail (foo, m, n) - zwracanie pierwszego m snd ostatniego n wierszy tekstu?
Ricardo
@ricardo, co wymagałoby napisania skryptu bash, który pobiera 3 argumenty i przekazuje je do funkcji taili / headlub, tworząc jej alias.
Paul
1

Napisałem prostą aplikację w Pythonie, aby to zrobić: https://gist.github.com/garyvdm/9970522

Obsługuje potoki (strumienie), a także pliki.

Gary van der Merwe
źródło
2
Najlepiej byłoby zamieścić odpowiednie części kodu.
fedorqui „SO przestań szkodzić”
1

opierając się na powyższych pomysłach (przetestowane bash i zsh)

ale używając aliasu „hat” Head and Tails

alias hat='(head -5 && echo "^^^------vvv" && tail -5) < '


hat large.sql
zzapper
źródło
0

Dlaczego nie użyć seddo tego zadania?

sed -n -e 1,+9p -e 190,+9p textfile.txt

lik
źródło
3
Działa to dla plików o znanej długości, ale nie dla plików o nieznanej długości.
Kevin
0

Aby obsłużyć potoki (strumienie), a także pliki, dodaj to do pliku .bashrc lub .profile:

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' ; }

Wtedy możesz nie tylko

headtail 10 < file.txt

ale również

a.out | headtail 10

(To nadal dodaje fałszywe puste wiersze, gdy 10 przekracza długość wejścia, w przeciwieństwie do zwykłego starego a.out | (head; tail). Dziękuję, poprzednie osoby odpowiadające).

Uwaga: headtail 10nie headtail -10.

Camille Goudeseune
źródło
0

Opierając się na tym, co @Samus_ wyjaśnił tutaj o tym, jak działa polecenie @Aleksandra Zalcman, ta odmiana jest przydatna, gdy nie można szybko zlokalizować początku ogona bez liczenia linii.

{ head; echo "####################\n...\n####################"; tail; } < file.txt

Lub jeśli zaczniesz pracować z czymś innym niż 20 liniami, liczba linii może nawet pomóc.

{ head -n 18; tail -n 14; } < file.txt | cat -n
Script Wolf
źródło
0

Aby wydrukować pierwsze 10 i ostatnie 10 wierszy pliku, możesz spróbować tego:

cat <(head -n10 file.txt) <(tail -n10 file.txt) | less

mariana.ft
źródło
0
sed -n "1,10p; $(( $(wc -l ${aFile} | grep -oE "^[[:digit:]]+")-9 )),\$p" "${aFile}"

UWAGA : Zmienna aFile zawiera pełną ścieżkę do pliku .

mark_infinite
źródło
0

Powiedziałbym, że w zależności od rozmiaru pliku, aktywne czytanie jego zawartości może nie być pożądane. W takiej sytuacji myślę, że wystarczy prosty skrypt powłoki.

Oto, jak ostatnio sobie z tym poradziłem w przypadku wielu bardzo dużych plików CSV, które analizowałem:

$ for file in *.csv; do echo "### ${file}" && head ${file} && echo ... && tail ${file} && echo; done

Spowoduje to wydrukowanie pierwszych 10 wierszy i ostatnich 10 wierszy każdego pliku, jednocześnie wypisując nazwę pliku i kilka wielokropków przed i po.

W przypadku jednego dużego pliku możesz po prostu uruchomić następujące polecenie, aby uzyskać ten sam efekt:

$ head somefile.csv && echo ... && tail somefile.csv
Jitsusama
źródło
0

Zużywa stdin, ale jest prosty i działa w 99% przypadków użycia

head_and_tail

#!/usr/bin/env bash
COUNT=${1:-10}
IT=$(cat /dev/stdin)
echo "$IT" | head -n$COUNT
echo "..."
echo "$IT" | tail -n$COUNT

przykład

$ seq 100 | head_and_tail 4
1
2
3
4
...
97
98
99
100
Brad Parks
źródło