Awk команда для печати всех строк, кроме последних трех строк

Я хочу напечатать все строки, кроме последних трех строк, только через awk. Обратите внимание, что мой файл содержит n строк.

Например,

file.txt содержит,

foo
bar
foobar
barfoo
last
line

Я хочу, чтобы вывод был,

foo
bar
foobar

Я знаю это может быть возможно с помощью комбинации tac и sed или tac и awk

$ tac file | sed '1,3d' | tac
foo
bar
foobar

$ tac file | awk 'NR==1{next}NR==2{next}NR==3{next}1' | tac
foo
bar
foobar

Но я хочу вывод только через awk.

5
задан 2 June 2014 в 15:28

4 ответа

Это очень неуклюже, но можно добавить каждую строку к массиву и в конце — когда Вы знаете, что длина — произвела все кроме последних 3 строк.

... | awk '{l[NR] = $0} END {for (i=1; i<=NR-3; i++) print l[i]}'

Другой (более эффективный здесь) подход вручную складывает в трех переменных:

... | awk '{if (a) print a; a=b; b=c; c=$0}'

a только печать после строки переместилась от c кому: b и затем в a таким образом, это ограничивает его тремя строками. Непосредственные позитивные аспекты - это, не хранит все содержание в памяти, и это не должно вызывать проблемы буферизации (fflush() после печати, если это делает), но оборотная сторона здесь, не просто увеличить масштаб этого. Если Вы хотите пропустить последние 100 строк, Вам нужны 100 переменных и 100 переменных фокусов.

Если awk имел push и pop операторы для массивов, это было бы легче.

Или мы могли предварительно вычислить количество строк и как далеко мы на самом деле хотим пойти с $(($(wc -l < file) - 3)). Это относительно бесполезно для переданного потоком содержания, но на файле, работает вполне прилично:

awk -v n=$(($(wc -l < file) - 3)) 'NR<n' file

Обычно говорящий Вы просто использовали бы head хотя:

$ seq 6 | head -n-3
1
2
3

Используя сравнительный тест terdon мы можем на самом деле видеть, как они выдерживают сравнение. Я думал, что предложу полное сравнение хотя:

  • head: 0,018 с (я)
  • awk + wc: 0,169 с (я)
  • awk 3 переменные: 0,178 с (я)
  • awk двойной файл: 0,322 с (terdon)
  • awk кольцевой буфер: 0,355 с (Scrutinizer)
  • awk для цикла: 0,693 с (я)

Быстрое решение использует утилиту C-optimised как head или wc обработайте тяжелые поднимающиеся вещи, но в чистом awk, вручную вращающийся стек является королем на данный момент.

16
ответ дан 2 June 2014 в 15:28

Для минимального использования памяти Вы могли использовать кольцевой буфер:

awk 'NR>n{print A[NR%n]} {A[NR%n]=$0}' n=3 file

При помощи оператора Mod на номерах строки мы имеем в большинстве n записей массива.

Взятие примера n=3:

На строке 1 NR%n равняется 1, строка 2 производит 2, и строка 3 производит 0, и строка 4 оценивает к 1 снова..

Line 1 -> A[1]
Line 2 -> A[2]
Line 3 -> A[0]
Line 4 -> A[1]
Line 5 -> A[2]
...

Когда мы добираемся для выравнивания 4, A[NR%n] содержит содержание строки 1. Таким образом, это печатается и A[NR%n] получает содержание строки 4. Следующая строка (строка 5) исходное содержание строки 2 печатается и так далее, пока мы не добираемся в конец. То, что остается непечатным, является содержанием буфера, который содержит последние 3 строки...

5
ответ дан 2 June 2014 в 15:28

Можно также обработать файл дважды, чтобы не сохранять что-либо в памяти:

awk '{if(NR==FNR){c++}else if(FNR<=c-3){print}}' file file

Прием здесь NR==FNR тест. NR текущий номер строки и FNR текущий номер строки текущего файла. Если больше чем один файл передается как вход, FNR будет равно NR только, в то время как первый файл обрабатывается. Таким образом, мы быстро получаем количество строк в первом файле и сохраняем его как c. Начиная с эти "два" файлы - на самом деле тот же, мы теперь знаем количество строк, которые мы хотим так, мы только печатаем, если это - один из них.

В то время как Вы могли бы думать, что это будет медленнее, чем другие подходы, это на самом деле быстрее, так как нет рядом ни с каким продолжением обработки. Все сделано с помощью внутреннего awk инструменты (NR и FNR) кроме единственного арифметического сравнения. Я протестировал на файле 50 МБ с одним миллионом строк, созданных с этой командой:

for i in {500000..1000000}; do 
    echo "The quick brown fox jumped over the lazy dog $i" >> file; 
done

Как Вы видите, времена почти идентичны, но подход, который я обеспечил здесь, является незначительно первым предложением более быстрого Oli (но медленнее, чем другие):

$ for i in {1..10}; do ( 
    time awk '{if(NR==FNR){c++}else if(FNR<=c-3){print}}' file file > /dev/null ) 2>&1 | 
       grep -oP 'real.*?m\K[\d\.]+'; 
  done | awk '{k+=$1}END{print k/10" seconds"}'; 
0.4757 seconds

$  for i in {1..10}; do ( 
    time awk '{l[NR] = $0} END {for (i=1; i<=NR-3; i++) print l[i]}' file > /dev/null ) 2>&1 | 
        grep -oP 'real.*?m\K[\d\.]+'; 
   done | awk '{k+=$1}END{print k/10" seconds"}'; 
0.5347 seconds
2
ответ дан 2 June 2014 в 15:28

Я знаю, что вопрос был конкретно приблизительно awk, но для краткости можно было всегда использовать:

head -n -3
0
ответ дан 7 October 2019 в 17:48

Другие вопросы по тегам:

Похожие вопросы: