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

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а не в консоль, а это совсем не то, что нам нужно.

Jander
источник
Когда голова или хвост заканчивают писать желаемый результат, они закрывают свой стандартный ввод и выходят. Вот откуда исходит 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
CurX
источник
Короче: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

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

l0b0
источник
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
nisetama
источник
это должен быть главный ответ! работает как шарм!
Бен Усман
1

Сегодня я столкнулся с чем-то вроде этого, где мне понадобилась только последняя строка и несколько строк от начала потока, и я пришел к следующему.

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

Я читаю это следующим образом: инициализирую пространство удержания с содержимым первой строки, добавляю строки 2-3 в пространство удержания, в EOF добавляю последнюю строку в пространство удержания, меняю пространство удержания и образца и печатаю образец Космос.

Возможно, кто-то с большим sed-fu, чем у меня, может понять, как обобщить это, чтобы напечатать последние несколько строк потока, указанного в этом вопросе, но мне это не понадобилось, и я не смог найти простой способ сделать математику на основе $адреса в sedили , возможно, управляя трюм так , что только за последние несколько строк в нем , когда EOFдостигаются.

deaks
источник
1

Вы можете попробовать Perl, если он установлен:

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

Это будет работать для большинства файлов, но считывает весь файл в память перед его обработкой. Если вы не знакомы с фрагментами Perl, «0» в квадратных скобках означает «взять первую строку», а «-3 ...- 1» означает «взять последние три строки». Вы можете адаптировать их обоих к вашим потребностям. Если вам нужно обрабатывать действительно большие файлы (то, что является «большим», может зависеть от вашей оперативной памяти и, возможно, размеров подкачки), вы можете пойти на:

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

это может быть несколько медленнее, потому что он делает срез на каждой итерации, но это не зависит от размера файла.

Обе команды должны работать как в каналах, так и с обычными файлами.

Jasio
источник