Я хочу напечатать все строки, кроме последних трех строк, только через 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.
Это очень неуклюже, но можно добавить каждую строку к массиву и в конце — когда Вы знаете, что длина — произвела все кроме последних 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
, вручную вращающийся стек является королем на данный момент.
Для минимального использования памяти Вы могли использовать кольцевой буфер:
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 строки...
Можно также обработать файл дважды, чтобы не сохранять что-либо в памяти:
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
Я знаю, что вопрос был конкретно приблизительно awk
, но для краткости можно было всегда использовать:
head -n -3