однострочный: вывести все строки, кроме последних 3?


Я хотел бы смоделировать GNU head -n -3, который печатает все строки, кроме последних 3, потому что head во FreeBSD не имеет этой функции. Поэтому я думаю о чем-то вроде

seq 1 10 | perl -ne ...

Здесь я использовал 10 строк, но это может быть любое число больше 3.

Можно ли это сделать на Perl или каким-то другим способом на FreeBSD в BASH?

Супер примитивное решение будет

seq 1 10 | sed '$d' | sed '$d' | sed '$d'
10 8

10 ответов:

Это работает как с каналом, так и с входным файлом:

seq 1 10 | perl -e'@x=<>;print@x[0..$#x-3]'
seq 1 10 | perl -e '@x=("")x3;while(<>){print shift @x;push @x,$_}'

Или

perl -e '@x=("")x3;while(<>){print shift @x;push @x,$_}' file

Или

command | perl -pe 'BEGIN{@x=("")x3}push @x,$_;$_=shift @x'
perl -pe 'BEGIN{@x=("")x3}push @x,$_;$_=shift @x' file
seq 1 10 | perl -ne 'push @l, $_; print shift @l if @l > 3'

Чистый Баш и простые инструменты (wc и cut):

head -n $(($(wc -l file | cut -c-8)-3)) file

Отказ от ответственности-у меня сейчас нет доступа к FreeBSD, но это работает на OSX bash.

Похоже, никто не использует sed и tac, поэтому вот один:

$ seq 10 | tac | sed '1,3d' | tac
1
2
3
4
5
6
7

Как насчет:

 seq 1 10 | perl -ne 'print if ( !eof  )' | perl -ne 'print if ( !eof  )' | perl -ne 'print if ( !eof  )' 

Этот awk one-liner, кажется, делает свою работу:

awk '{a[NR%4]=$0}NR>3{print a[(NR-3)%4]}' file

Или сделайте это с bash самостоятельно, если у вас есть версия 4.0 или новее:

seq 1 10 | (readarray -t LINES; printf '%s\n' "${LINES[@]:(-3)}")

Обновление: в этом случае будут удалены последние три строки, а не показаны только они.

seq 1 10 | (readarray -t L; C=${#L[@]}; printf '%s\n' "${L[@]:0:(C > 3 ? C - 3 : 0)}")

Для удобства его можно поместить на функцию:

function exclude_last_three {
    local L C
    readarray -t L; C=${#L[@]}
    printf '%s\n' "${L[@]:0:(C > 3 ? C - 3 : 0)}"
}

seq 1 10  | exclude_last_three
seq 11 20 | exclude_last_three

Вот запоздалый ответ, потому что вчера я столкнулся с чем-то подобным.

Это решение:

  • чистый Баш
  • однострочный
  • считывает входной поток только один раз
  • считывает входной поток построчно, а не все сразу

Протестировано на Ubuntu, Redhat и OSX.

$ seq 1 10 | { n=3; i=1; while IFS= read -r ln; do [ $i -gt $n ] && cat <<< "${buf[$((i%n))]}"; buf[$((i%n))]="$ln"; ((i++)); done; }
1
2
3
4
5
6
7
$ 
Он работает путем считывания строк в циклический буфер, реализованный в виде N-элементного массива.

N-число строк, которые нужно отрезать от конца строки. файл.

Для каждой строки i, которую мы читаем, мы можем воспроизвести строку i-n из кругового буфера, а затем сохранить строку i в круговом буфере. Ничто не повторяется до тех пор, пока не будут прочитаны первые n строк. (I mod n) - это индекс в массиве, который реализует циклический буфер.

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

Еще одно решение Awk, которое использует только минимальное количество буферов и быстро печатает строки, не читая сначала все строки. Его можно также использовать с трубами и большими файлами.

awk 'BEGIN{X = 3; for(i = 0; i < X; ++i)getline a[i]}{i %= X; print a[i]; a[i++] = $0}'