Команда відобразити перші кілька та останні кілька рядків файлу


23

У мене є файл з багатьма рядками, і кожен рядок має часову позначку на початку, як

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

Отже, я часто перевіряю 2 речі з цього файлу журналу.

  1. Перші рядки, що мають глобальні умови та час початку, також наведені.
  2. Останні кілька рядків, які мають статус виходу з деякою іншою інформацією.

Чи є якась швидка зручна одна команда, яка могла б дозволити мені відобразити лише перші та останні кілька рядків файлу?


2
Які глобальні умови, і не head and tailпрацює для вас?
ромашка

Це частина мого файлу журналу. Я намагався бути досконалим. Ви можете проігнорувати це.
mtk

Ваше рішення мені добре виглядає. Якщо ви хочете більше зручності, перетворіть його на функцію оболонки (навіть псевдонім це може зробити).
vonbrand

@vonbrand Проблема в тому, що я не знаюN
Бернхард

@Bernhard, я не sed(1)експерт, але є способи приховати речі для подальшого використання з ним. Можливо, це окупиться заглянути туди. OTOH, я, мабуть, підхоплюю сценарій Perl (або будь-який інший), щоб це робити, якщо його часто використовувати, оскільки я з цим більше знайомий.
vonbrand

Відповіді:


12

Ви можете використовувати sedабо awkзробити це однією командою. Однак ви втратите швидкість, викликайте sedі вам awkдоведеться все одно пробігати весь файл. З точки зору швидкості набагато краще робити функцію або кожен раз комбінувати tail+ head. У цього випадку є недолік роботи, якщо вхід - це труба, проте ви можете використовувати заміну процесора, якщо ваша оболонка підтримує це (подивіться приклад нижче).

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

і просто запустити його як

first_last "/path/to/file_to_process"

продовжувати процес заміщення (bash, zsh, ksh, як оболонки):

first_last <( command )

пс. ви навіть можете додати "a", grepщоб перевірити, чи існують ваші "глобальні умови".


-n 10це за замовчуванням, ні?
l0b0

@ l0b0 так, це за замовчуванням. -n 10Тут не потрібно.
пік

20

@rush має рацію в тому, що використання head + tail є більш ефективним для великих файлів, але для невеликих файлів (<20 рядків) деякі рядки можуть виводитися вдвічі.

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

було б однаково ефективно, але не було б проблеми вище.


На відміну від рішення Rushs, це не працює в оболонці POSIX.
Марко

2
@Marco Huh? Тут використовуються лише конструкції POSIX. Що ти бачиш не так?
Жил "ТАК - перестань бути злим"

2
@Gilles Я пропустив простір: {head; tail;} < fileпрацює в zsh, але не в роботі sh. { head; tail;} < fileзавжди працює. Вибачте за шум.
Марко

@Marco, якби були проблеми з цим, це було б head, а не оболонкою. POSIX вимагає headзалишити курсор у файлі лише минулих 10 рядків для звичайних файлів. Проблема може виникнути для не-POSIX- headреалізацій (дуже старі версії голови GNU використовувались як невідповідні у цьому випадку, але ми говоримо десятиліттями) або якщо файл не шукається (наприклад, названа труба чи сокет, але тоді інше рішення матиме ту саму проблему).
Стефан Шазелас

1
@FCTW,sudo sh -c '{ head; tail;} < /path/to/file'
Стефан Шазелас

9

{ head; tail; }Рішення не буде працювати на труби (або сокетах або які - небудь інші , які будуть недоступні для пошуку файлів) , тому що headможе споживати занадто багато даних , як він читає блоки і не може шукати назад на трубі потенційно залишаючи курсор всередині файлу за те , що tailмається на увазі вибрати.

Отже, ви можете використовувати інструмент, який читає один символ одночасно, як оболонки read(тут використовується функція, яка приймає кількість аргументів заголовків і рядків хвоста як аргументи).

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

або реалізувати tailу awk, наприклад, як:

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]
    }'
}

З sed:

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

(хоч слідкуйте за тим, що деякі sedреалізації мають низьке обмеження на розмір простору шаблону, тому не вдасться отримати великі значення кількості ліній хвоста).


4

Використовуючи bashпідстановку процесу, ви можете зробити наступне:

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

Зауважте, що рядки не гарантовані в порядку, хоча для файлів довше приблизно 8 кБ вони, швидше за все, будуть. Це відсікання 8 КБ є типовим розміром буфера для читання і пов'язане з причиною, | {head; tail;}що не працює для невеликих файлів.

cat >/dev/nullНеобхідно тримати headтрубопровід в живих. Інакше teeрано вийдете, і поки ви отримаєте вихід tail, це буде десь із середини введення, а не з кінця.

Нарешті, чому >/dev/nullзамість, скажімо, переходу tailдо іншого |? У наступному випадку:

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

head's stoutout подається в трубу, tailа не в консоль, що зовсім не те, що ми хочемо.


Коли голова або хвіст закінчують писати потрібний вихід, вони закривають свій stdin і виходять. Ось звідки походить СИГПІП. Зазвичай це хороша річ, вони відкидають решту продукції, тому немає жодної причини, щоб інша сторона труби продовжувала витрачати час на її створення.
дероберт

Що робить наказ, ймовірно, підтриманим? Ймовірно, це буде для великого файлу, тому що tailвін повинен працювати довше, але я очікую (і я бачу), що він не встигне приблизно половини часу для коротких вводів.
Жил "ТАК - перестань бути злим"

Ви отримаєте SIGPIPE з tee >(head) >(tail)тих самих причин ( >(...)що, до речі, є функцією ksh, яку зараз підтримують і zsh, і bash), також використовує труби. Ви можете зробити це, ... | (trap '' PIPE; tee >(head) >(tail) > /dev/null)але ви все ще побачите деякі пошкоджені повідомлення про помилки трубtee .
Стефан Шазелас

У моїй системі (bash 4.2.37, coreutils 8.13) - tailце той, хто вбивається SIGPIPE, а не teeі tailне записує в трубу. Так це повинно бути з kill(), так? І це відбувається лише тоді, коли я використовую |синтаксис. straceкаже, що teeне дзвонить kill()... так може бути bash?
Джендер

1
@Jander, спробуйте годувати більше 8 к. На кшталтseq 100000 | tee >(head -n1) >(tail -n1) > /dev/null
Stéphane Chazelas

3

Використання ed(яке буде читати весь файл в оперативній пам'яті):

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

Коротше:ed -s file <<< $'11,$-10d\n,p\nq\n'
don_crissti

2

Перше рішення Stephane у функції, щоб ви могли використовувати аргументи (працює в будь-якій оболонці Bourne або POSIX):

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

Тепер ви можете зробити це:

head_tail -n 5 < /path/to/file

Звичайно, це передбачає, що ви дивитесь лише на один файл і вам подобається, що рішення Stephane працює (надійно) лише на звичайних (шуканих) файлах.


2

За допомогою параметра -u( --unbuffered) GNU sedви можете використовувати sed -u 2qяк незаблоковану альтернативу head -n2:

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

(head -n2;tail -n2)не вдається, коли останні рядки є частиною блоку вводу, який споживається head:

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

це має бути головна відповідь! працює як шарм!
Бен Усман

1

Я натрапив на щось подібне сьогодні, де мені знадобився лише останній рядок і кілька рядків з передньої частини потоку і придумав наступне.

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

Я читаю це як: ініціалізуйте простір утримування вмістом першого рядка, додайте рядки 2-3 у простір утримування, у EOF додайте останній рядок до місця утримування, поміняйте простір утримування та викрійки та надрукуйте шаблон простір.

Можливо, хтось із більш sed-фу, ніж у мене, може придумати, як це узагальнити, щоб надрукувати останні кілька рядків потоку, зазначених у цьому запитанні, але мені це не було потрібно і не вдалося знайти простий спосіб зробити математику за $адресою в sedабо, можливо, керуючи простором утримування, щоб у ньому знаходилися лише останні кілька рядків EOF.


1

Ви можете спробувати Perl, якщо він встановлений:

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

Це буде працювати для більшості файлів, але читає весь файл в пам'ять перед його обробкою. Якщо ви не знайомі з фрагментами Perl, "0" у квадратних дужках означає "взяти перший рядок", а "-3 ...- 1" означає "взяти останні три рядки". Ви можете налаштувати їх обох під свої потреби. Якщо вам потрібно обробити дійсно великі файли (те, що є «великим», може залежати від вашої оперативної пам’яті та, можливо, розмірів підкачки), ви можете скористатися:

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

це може бути дещо повільніше, оскільки він робить зріз кожної ітерації, але це не залежить від розміру файлу.

Обидві команди повинні працювати як у трубах, так і зі звичайними файлами.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.