Сначала обработайте последнюю строку, используя awk

11

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

Следующий метод, использующий tacдважды, делает работу, но, может быть, более сложный, чем необходимо.

$ cat file
0 5
1 2
2 3
3 4
$ tac file | awk 'NR==1{norm=$2} {print $1, $2/norm}' | tac
0 1.25
1 0.5
2 0.75
3 1

Мой вопрос заключается в следующем: возможно ли получить вышеуказанный результат, используя только awk?

Я думаю, что ответ «Нет, awk сканирует файл построчно», но я открыт для предложений по альтернативам.

Бернхард
источник

Ответы:

5

Вы можете сделать это как двухпроходное решение в awk:

awk 'FNR == NR { n = $2; next } { print $1, $2/n }' infile infile

Если ваша версия awk поддерживает блок ENDFILE (например, GNU awk 4+), вы можете сделать это следующим образом:

awk 'ENDFILE { n = $2 } FNR != NR { print $1, $2/n }' infile infile

Обратите внимание, что более эффективно до seekконца файла сначала увидеть ответ camh .

объяснение

Первый пример работает, помня предыдущий $2, то есть он оценивается только тогда, когда счетчик локальной строки ( FNR) равен глобальному счетчику строки ( NR). Команда nextпереходит на следующую строку, в этом случае она гарантирует, что последний блок оценивается только при разборе второго аргумента.

Второй пример имеет похожую логику, но использует преимущество блока ENDFILE, который оценивается, когда достигается конец входного файла.

Тор
источник
Первый пример работает нормально, второй - нет $ awk --version GNU Awk 3.1.8. Можете ли вы добавить очень маленькое объяснение того, как обрабатываются два входных файла и что nextделает?
Бернхард
1
@Bernhard: см. Редактирование
Thor
6

Если ваш источник данных - это файл, который можно прочитать несколько раз (т. Е. Это не поток), вы должны сначала использовать tail(1)данные, которые вы хотите получить из последней строки, и передать их в awk для последовательной обработки файла. tailбудет стремиться к концу файла, чтобы прочитать последнюю строку, без необходимости читать все данные перед этим.

awk -v norm=$(tail -n 1 file | cut -d' ' -f2) '{print $1, $2/norm}' file

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

CAMH
источник
3

Вы можете загрузить их в массив и прочитать в обратном направлении:

awk '{x[i++]=$0} END{for (j=i-1; j>=0;) print x[j--] }'

Вы могли бы сделать это более эффективно, но этот вид иллюстрирует, почему awkэто не правильный инструмент для этого. Продолжайте использовать, tacгде возможно, GNU tac, как правило, самый быстрый из множества инструментов для этой работы.

Крис Даун
источник
Я согласен, использование for-loops в awkне является решением.
Бернхард