Команда для отображения первых и последних нескольких строк файла


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
— Бернхард

@ Бернхард, я не 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 )

пс. Вы даже можете добавить, grepчтобы проверить, существуют ли ваши «глобальные условия».


-n 10по умолчанию нет?
— 10

@ l0b0 да, это по умолчанию. -n 10здесь не нужно
— Раш

20

@rush прав в том, что использование head + tail более эффективно для больших файлов, но для маленьких файлов (<20 строк) некоторые строки могут выводиться дважды.

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

было бы одинаково эффективно, но не было бы проблемы выше.


В отличие от решения Rushs, это не работает в оболочке POSIX.
— Марко

2
@ Марко А? Здесь используются только конструкции POSIX. Что вы видите не так?
— Жиль "ТАК - перестань быть злым"

2
@ Жиль Я пропустил место: {head; tail;} < fileработает в Zsh, но не в Sh. { head; tail;} < fileвсегда работает. Извините за шум.
— Марко

@ Марко, если бы с этим были проблемы, то это было бы 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

headStdout подается в канал, tailа не в консоль, а это совсем не то, что нам нужно.


Когда голова или хвост заканчивают писать желаемый результат, они закрывают свой стандартный ввод и выходят. Вот откуда исходит SIGPIPE. Обычно это хорошая вещь, они отбрасывают оставшуюся часть выходных данных, поэтому у другой стороны канала нет причин продолжать тратить время на его создание.
— Дероберт

Что делает заказ, вероятно, будет поддержан? Вероятно, это будет для большого файла, потому что tailон должен работать дольше, но я ожидаю (и вижу), что он потерпит неудачу около половины времени для коротких входных данных.
— Жиль "ТАК - перестань быть злым"

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

В моей системе (bash 4.2.37, coreutils 8.13) tailSIGPIPE убивает tee, а tailне пишет в канал. Значит, это должно быть от а kill()? И это происходит только тогда, когда я использую |синтаксис. straceговорит, что teeне звонит kill()... так может bash?
— Jander

1
@Jander, попробуйте кормить больше, чем 8k какseq 100000 | tee >(head -n1) >(tail -n1) > /dev/null
— Стефан Chazelas

3

Используя ed(который прочитает весь файл в RAM все же):

# 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

Первое решение Стефана в функции, чтобы вы могли использовать аргументы (работает в любой Bourne-подобной или POSIX-оболочке):

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

Теперь вы можете сделать это:

head_tail -n 5 < /path/to/file

Это, конечно, предполагает, что вы просматриваете только один файл, и, как решение Стефана, работает (надежно) только с обычными (доступными для поиска) файлами.


2

С опцией GNU -u( --unbuffered) 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-fu, чем у меня, может понять, как обобщить это, чтобы напечатать последние несколько строк потока, указанного в этом вопросе, но мне это не понадобилось, и я не смог найти простой способ сделать математику на основе $адреса в 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.