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


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.