Slurp-режим в awk?

16

Такие инструменты , как sed, awkили perl -nобработать их ввода одной записи в то время, записи быть строки по умолчанию.

Некоторые, например, awkс помощью RSGNU sedс -zили perlс, -0oooмогут изменить тип записи, выбрав другой разделитель записей.

perl -nможет сделать весь ввод (каждый отдельный файл, когда передано несколько файлов) единственной записью с -0777опцией (или -0после любого восьмеричного числа больше, чем 0377, 777 является каноническим). Это то, что они называют режимом Slurp .

Может ли нечто подобное быть сделано с помощью awks RSили любого другого механизма? Где awkобрабатывает содержимое каждого файла в целом, в отличие от каждой строки каждого файла?

Стефан Шазелас
источник

Ответы:

15

Вы можете использовать разные подходы в зависимости от того, awkобрабатывает ли RSон один символ (как awkэто делают традиционные реализации) или как регулярное выражение (как gawkили mawkделает). Пустые файлы также сложны, чтобы их можно awkбыло пропустить.

gawk, mawkИли другие awkреализации , где RSмогут быть регулярным выражением.

В этих реализациях (например mawk, помните, что некоторые ОС, такие как Debian, поставляют очень старую версию вместо современной, поддерживаемой @ThomasDickey ), если RSсодержит один символ, разделитель записей является этим символом или awkвходит в режим абзаца, когда RSон пуст, или иначе рассматривается RSкак регулярное выражение.

Решение в том, чтобы использовать регулярное выражение, которое невозможно сопоставить. Некоторые приходят в голову как x^или $x( xдо начала или после конца). Однако некоторые (особенно с gawk) дороже, чем другие. До сих пор я обнаружил, что ^$это самый эффективный. Он может совпадать только на пустом входе, но тогда нечего сопоставлять.

Итак, мы можем сделать:

awk -v RS='^$' '{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

Одно предостережение заключается в том, что он пропускает пустые файлы (в отличие от perl -0777 -n). Это можно решить с помощью GNU awk, поместив код в ENDFILEоператор. Но нам также необходимо выполнить сброс $0в операторе BEGINFILE, так как иначе он не будет сброшен после обработки пустого файла:

gawk -v RS='^$' '
   BEGINFILE{$0 = ""}
   ENDFILE{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

традиционные awkреализации, POSIXawk

В них RSтолько один символ, у них нет BEGINFILE/ ENDFILE, у них нет RTпеременной, они также обычно не могут обработать символ NUL.

Можно подумать, что использование RS='\0'может работать тогда, так как в любом случае они не могут обрабатывать ввод, содержащий байт NUL, но нет, который RS='\0'в традиционных реализациях рассматривается как RS=режим абзаца.

Одним из решений может быть использование символа, который вряд ли можно найти во входных данных, например \1. В многобайтовых символьных локалях вы можете даже сделать это байтовыми последовательностями, которые очень маловероятны, поскольку они образуют символы, которые не назначены или не являются символами, как $'\U10FFFE'в локалях UTF-8. Не очень надежный, и у вас есть проблема с пустыми файлами.

Другое решение может состоять в том, чтобы сохранить весь ввод в переменной и обработать его в конце оператора END. Это означает, что вы можете обрабатывать только один файл за раз:

awk '{content = content $0 RS}
     END{$0 = content
       printf "%s: <%s>\n", FILENAME, $0
     }' file

Это эквивалент sed's:

sed '
  :1
  $!{
   N;b1
  }
  ...' file1

Еще одна проблема с этим подходом является то , что , если файл не заканчивается символом перевода строки (а не пустой), один по - прежнему произвольно добавляют $0в конце (с gawk, вы бы работать вокруг этого, используя RTвместо того , чтобы RSв код выше). Одним из преимуществ является то, что у вас есть запись о количестве строк в файле в NR/ FNR.

Стефан Шазелас
источник
что касается последней части («если файл не заканчивался символом новой строки (и не был пустым), он все равно произвольно добавляется в конце в $ 0»): для текстовых файлов они должны иметь окончание новая линия. Например, vi добавляет его и, таким образом, изменяет файл при его сохранении. Отсутствие завершающего символа новой строки приводит к тому, что некоторые команды отбрасывают последнюю «строку» (например, wc), а другие все еще «видят» последнюю строку ... ymmv. Поэтому ваше решение действительно, imo, если вы должны обрабатывать текстовые файлы (что, вероятно, имеет место, поскольку awk хорош для обработки текста, но не так хорош для двоичных файлов ^^)
Olivier Dulac
1
попытка выпить олл-ин может натолкнуться на некоторые ограничения ... Традиционный awk, по-видимому, имел (имеет?) ограничение в 99 полей на строке ... поэтому вам может понадобиться использовать другую FS, чтобы избежать этого ограничения, но вы можете также есть лимиты на то, какой длины может быть общая длина строки (или всего, если вам удастся получить все это на одной строке)?
Оливье Дюлак
напоследок: (глупый ...) хак может состоять в том, чтобы сначала проанализировать весь файл и найти символ, которого там нет, затем tr '\n' 'thatchar' файл перед отправкой его в awk и tr 'thatchar' \n'вывод? (возможно, вам все равно придется добавить новую строку, чтобы убедиться, что, как я уже отмечал выше, ваш входной файл имеет завершающий символ новой строки: { tr '\n' 'missingchar' < thefile ; printf "\n" ;} | awk ..... | { tr 'missingchar' '\n' }(но в конце добавьте '\ n', от которого вам, возможно, придется избавиться ... возможно, добавление sed перед последним tr? если этот tr принимает файлы без завершения новой строки ...)
Olivier Dulac
@OlivierDulac, ограничение на количество полей будет действовать только в том случае, если мы обращаемся к NF или любому другому полю. awkне делает разделения, если мы не делаем. Сказав это, даже в /bin/awkSolaris 9 (основанном на awk 1970-х годов) такого ограничения не было, поэтому я не уверен, что мы сможем найти такое, которое имеет (все еще возможно, так как oawk SVR4 имел предел 99 и nawk 199, так что это Скорее всего, Sun добавил снятие этого лимита, и его нельзя найти в других awk-играх на базе SVR4. Можете ли вы протестировать его в AIX?).
Стефан Шазелас