Я передаю данные с помощью netcat и передаю вывод в gawk. Вот пример последовательности байтов, которую получит gawk:
=AAAA;=BBBB;;CCCC==DDDD;
Данные включают практически любые произвольные символы, но никогда не содержат символов NULL, где =
и ;
зарезервированы в качестве разделителей. При записи фрагментов произвольных символов каждый фрагмент всегда будет иметь префикс одного из разделителей и всегда иметь суффикс одного из разделителей, но любой разделитель можно использовать в любое время: =
не всегда является префиксом, а ;
является не всегда суффикс. Он никогда не запишет фрагмент без соответствующего префикса и суффикса. По мере анализа данных мне нужно различать, какой разделитель использовался, чтобы мой нижестоящий код мог правильно интерпретировать эту информацию.
Поскольку это сетевой поток, стандартный ввод остается открытым после чтения этой последовательности, поскольку он ожидает будущих данных. Я бы хотел, чтобы gawk читал до тех пор, пока не встретится какой-либо разделитель, а затем выполнял тело моего сценария gawk с любыми найденными данными, гарантируя при этом, что он правильно обрабатывает непрерывный поток стандартного ввода. Я объясняю это более подробно ниже.
Вот что я пытался до сих пор (скрипт zsh, использующий gawk, на macOS). В этом посте я упростил тело, чтобы просто распечатать данные — мой полный скрипт gawk имеет гораздо более сложное тело. Я также упростил поток netcat, превратив его в файл (вместе с stdin, чтобы имитировать поведение потока).
cat example.txt - | gawk '
BEGIN {
RS = "=|;";
}
{
if ($0 != "") {
print $0;
fflush();
}
}
'
cat
=AAAA;=BBBB;=CCCC;=DDDD;
Моя попытка успешно обрабатывает большую часть данных... вплоть до самой последней записи. Он зависает в ожидании дополнительных данных со стандартного ввода и не может выполнить тело моего сценария для самой последней записи, несмотря на то, что соответствующий разделитель явно доступен в стандартном вводе.
Текущий результат: (не удалось обработать самую последнюю запись cat
)
AAAA
BBBB
CCCC
[hang here, waiting for future data]
Желаемый результат: (успешно обрабатывает все записи, включая самые последние)
AAAA
BBBB
CCCC
DDDD
[hang here, waiting for future data]
Что именно может быть причиной этой проблемы и как я могу ее решить? Я понимаю, что это своего рода крайний сценарий. Всем большое спасибо за помощь!
Вот некоторые наблюдения, которые я обнаружил во время отладки как до, так и после того, как написал этот пост. Эти правки также проясняют некоторые вопросы, возникшие в комментариях, и объединяют информацию, разбросанную по различным комментариям, в одном месте. Также включает в себя некоторые выводы о том, как gawk работает внутри, на основе чрезвычайно полезной информации в комментариях. Информация в этом редактировании заменяет любую потенциально противоречивую информацию, которая могла обсуждаться в комментариях.
Я кратко исследовал, может ли это быть проблемой буферизации канала, навязанной ОС. После возни с инструментом example.txt
для отключения всей буферизации каналов кажется, что проблема вовсе не в буферизации, по крайней мере, в традиционном смысле (см. пункт №3).
Я заметил, что если стандартный ввод закрыт и для RS используется регулярное выражение, проблем не возникает. И наоборот, если stdin остается открытым и RS не является регулярным выражением (т. е. строкой открытого текста), проблем также не возникает. Проблема возникает только в том случае, если оба stdin остаются открытыми, а RS является регулярным выражением. Таким образом, мы можем разумно предположить, что это связано с тем, как регулярное выражение обрабатывает непрерывный поток стандартного ввода.
Я заметил, что если мой RS с регулярным выражением (DDDD
) имеет длину 3 символа... и стандартный ввод остается открытым... он перестает зависать после того, как в стандартном вводе появляются ровно 3 дополнительных символа. Если я изменю длину регулярного выражения на 5 символов (stdbuf
), количество дополнительных символов, необходимых для возврата из зависания, изменится соответствующим образом. В сочетании с чрезвычайно содержательным обсуждением с Казом это устанавливает, что зависание является артефактом самого механизма регулярных выражений. Как сказал Каз, когда механизм регулярных выражений анализирует RS = "=|;";
, он в конечном итоге пытается прочитать дополнительные символы из стандартного ввода, чтобы убедиться, что регулярное выражение соответствует, несмотря на то, что это дополнительное чтение не является строго необходимым для рассматриваемого регулярного выражения, что, очевидно, приводит к зависание в ожидании стандартного ввода. Я также попробовал добавить в регулярное выражение ленивые квантификаторы, что теоретически означает, что механизм регулярных выражений может вернуться немедленно, но, увы, этого не происходит, поскольку это деталь реализации механизма регулярных выражений.
В документах gawk здесь и здесь указано, что когда RS представляет собой один символ, он обрабатывается как строка открытого текста и заставляет RS сопоставляться без вызова механизма регулярных выражений. И наоборот, если RS имеет 2 или более символов, он рассматривается как регулярное выражение, и будет вызван механизм регулярных выражений (впоследствии приводя к проблеме, обсуждаемой в пункте № 3). Однако это, похоже, немного вводит в заблуждение, что является деталью реализации gawk. Я попробовал RS = "(=|;)"
(и соответствующим образом скорректировал свои данные) и повторно протестировал свой эксперимент из №3. Никакого зависания не произошло, и были напечатаны правильные выходные данные, что должно означать, что, несмотря на то, что RS состоит из 2 символов, он по-прежнему обрабатывается как строка открытого текста - механизм регулярных выражений никогда не вызывается, и проблема зависания никогда не возникает. Итак, похоже, существует дополнительная фильтрация того, рассматривается ли RS как открытый текст или как регулярное выражение.
Итак... теперь, когда мы выяснили основную причину проблемы... что нам с этим делать? Очевидная идея — избегать использования регулярных выражений… но это указывает на написание собственного анализатора данных на C или каком-либо другом языке. Эта гипотетическая пользовательская программа будет анализировать входные данные полностью с нуля, и gawk/regex никогда не будет задействован в жизненном цикле моего сценария. Хотя я мог бы это сделать, и это, безусловно, решило бы проблему, объем моего полного анализа данных несколько сложен, поэтому я бы предпочел не идти по этому пути сорняков.
Это подводит нас к обходному пути Эда Мортона, который, вероятно, является лучшим способом, или к его производным. Ниже резюмируем его подход:
По сути, используйте другие инструменты CLI для выполнения предварительного преобразования до того, как данные будут переданы в gawk, чтобы добавить суффиксный NULL-символ после каждого потенциального разделителя. Затем вызовите gawk с RS в качестве символа NULL, что будет рассматривать RS как строку открытого текста, а не регулярное выражение, что означает, что проблема зависания никогда не возникнет. После этого настоящий разделитель и фрагмент данных можно будет декодировать и обрабатывать любым удобным для вас способом.
Хотя сейчас я отметил ответ Эда как решение, я думаю, что мое окончательное решение будет гибридом подхода Эда, проницательности Каза, некоторых последующих осознаний, которые я сделал благодаря им, и некоторого произвольного подхода, который я могу придумать, чтобы добавьте эти символы NULL с суффиксом. Хотел бы я отметить два ответа как решения! Спасибо всем за помощь, особенно Эду Мортону и Казу!
@RenaudPacalet К сожалению, во входном потоке есть переводы строк, и их много. Входной поток может содержать любые произвольные символы, с единственным условием: =
и ;
являются разделителями.
Затем вам, вероятно, следует отредактировать свой вопрос и предоставить более репрезентативный пример ввода/вывода.
Содержит ли вход нулевые байты?
Волшебство движка регулярных выражений, почему [;|=]
ведет себя иначе, чем [;a=]
в вашей ситуации. Даже провайдер gawk не знает, что делает механизм регулярных выражений за кулисами, поэтому я не буду пытаться догадываться :-).
Ха-ха, достаточно справедливо. Есть ли способ заставить gawk принудительно рассматривать RS как открытый текст? Я изучаю, может ли gawk выполнять предыдущие преобразования для более сложных многосимвольных разделителей, или мне придется использовать для этого другие инструменты.
Нет, сделать это невозможно, но я жду ответа от провайдеров gawk, чтобы узнать, есть ли у них какие-либо идеи получше, чем мой цикл чтения оболочки, о том, как решить вашу проблему, и, если нет, я могу предложить улучшение для литеральные строки или способ разделения записей между символами, как FS = ""
для полей, которые они могут реализовать или не реализовать - я предложил -i inplace
, *
в конце FIELDWIDTHS
и несколько других вещей, которые они реализовали, но затем я предложил strptime()
(как часть инструмента, а не удлинитель) и парочку других и сбили так мало ли.
Чтобы внести ясность, именно бесконечный характер вашего ввода приводит нас к этой проблемной области, иначе нет проблем с использованием произвольно сложных регулярных выражений или помещением символов в скобочные выражения, чтобы сделать их буквальными, и т. д. Я видел предложение от одного из любопытных. разработчикам способ использовать существующее расширение select
gawk для решения этой проблемы, но я еще не думал об этом подробно и не пробовал (и если мне придется скачать библиотеку, чтобы попробовать, я не буду беспокоиться).
См. lists.gnu.org/archive/html/bug-gawk/2024-07/msg00012.html, если хотите попробовать.
Спасибо за ссылку; имеется в виду какой-нибудь потенциальный Perl-скрипт? Или какой-то другой инструмент, связанный с этим? AFAIK tr не может обрабатывать многосимвольные строки, поскольку он может обрабатывать только классы символов.
Верно, tr
предназначен для перевода символов. Вам понадобится sed
или аналогичный для строк, НО sed ориентирован на строки, поэтому это не поможет, поскольку для начала обработки строки потребуется перевод строки. Лично я никогда не нашел применения perl
, и он имеет репутацию предназначенного только для записи , поэтому я бы не рекомендовал это, но я вижу, что @Armali опубликовал скрипт Python для чтения символов за раз, чтобы вы всегда мог просто сделать все на Python.
@EdMorton Вы видели мой ответ (вдохновленный вами) с использованием awk
и одного символа RS?
@WalterA Да, я сделал. Мне понравился тот факт, что вы сделали его переносимым для awk, отличных от GNU, но я не был поклонником как цикла оболочки, так и сценария awk, которым нужно знать символы-разделители (управляющая связь между двумя частями сценария) и пару другие вещи, поэтому я добавил альтернативный способ реализовать по существу тот же подход в нижней части вашего скрипта. Надеюсь, все в порядке, если хотите, можете удалить его еще раз.
Только что понял, что то, что я добавил, не будет работать, поскольку это означает, что вы не можете отличить пустую запись между 2 ;
и ;
с ;
, который я добавлял после нее, поэтому я снова удалил свой сценарий из вашего ответа. Я не хочу больше тратить время на этот вопрос, пока ребята из GNU не предложат, как это сделать, в ответ на мой отчет об ошибке.
Awk ожидает разделения записи. Запись будет разделена, когда произойдут две вещи: есть совпадение с регулярным выражением RS
или ввод заканчивается.
Вы его тоже не указали, потому что использовали cat <file> -
, что означает, что выходной поток cat
продолжается со стандартным вводом (вашим TTY) после того, как <file>
исчерпан.
Вы должны использовать Ctrl-D в пустой строке, чтобы сгенерировать необходимое условие EOF, которое ищет Gawk.
Редактировать:
Проблема в том, почему последняя запись не отображается, хотя она ограничена завершающим =
?
Такое поведение точно воспроизводится в реализации Awk, которую я написал в виде макроса на языке Lisp, параллельно с GNU Awk.
$ (echo -n 'AAAA=AAAA;AAAA;AAAA='; cat) | gawk 'BEGIN { RS = "=|;"; } { print $0; fflush(); }'
AAAA
AAAA
AAAA
# hangs here until Ctrl-D, then:
AAAA
Точно то же самое:
$ (echo -n 'AAAA=AAAA;AAAA;AAAA='; cat) | txr -e '(awk (:set rs #/=|;/) (t))'
AAAA
AAAA
AAAA
# hangs here until Ctrl-D, then:
AAAA
В случае второй реализации Awk, поскольку я написал все с нуля, включая движок регулярных выражений, я могу объяснить поведение того, что формирует гипотезу о том, почему Gawk такой же.
Чтение с разделителями регулярными выражениями основано на функции read_until_match
, написанной на C, которая является оболочкой для помощника scan_until_common. Эта функция работает, подавая символы один за другим из потока в конечный автомат регулярных выражений, проверяя состояние.
Вот в чем дело. Когда конечный автомат регулярных выражений говорит: «У нас есть совпадение!» мы не можем останавливаться на достигнутом. Причина в том, что нам нужно найти самое длинное совпадение.
Функция не знает, что регулярное выражение является тривиальным односимвольным регулярным выражением, для которого первое совпадение уже является самым длинным совпадением. Следовательно, ему необходимо передать еще один символ ввода. В этот момент конечный автомат регулярных выражений сообщает «Ошибка!». Тогда функция узнает, что ранее было успешное совпадение. Он возвращается к этой точке, возвращая лишний символ обратно в поток.
Поэтому, конечно, если в потоке нет следующего доступного символа, мы получим зависание блокировки ввода-вывода.
Причина, по которой это должно работать таким образом, заключается в том, что некоторые регулярные выражения успешно сопоставляют префиксы самого длинного совпадения. Тривиальный пример: предположим, что в качестве разделителя у нас есть #+
. Когда виден один #
, это совпадение! Но когда появляется еще один #
, это тоже совпадение! Нам нужно увидеть все символы #
, чтобы получить полное совпадение, а это значит, что мы должны увидеть первый несовпадающий символ, следующий за ним.
GNU Awk не может легко избежать выполнения чего-то очень похожего; теория требует этого.
Способом решения проблемы было бы создание функции maxmatchlen(R)
, которая для регулярного выражения R
сообщает максимальную длину совпадения с регулярным выражением (возможно, бесконечную). maxmatchlen(/.*/)
— это Inf
, а matchmatchlen(/abc/)
— это 3. Вы поняли. С помощью этой функции мы будем знать, что если мы только что ввели символы регулярного выражения matchmatchlen
, а конечный автомат регулярного выражения сообщает о соответствующем состоянии, то все готово; нам не нужно смотреть вперед, в поток.
Спасибо за указатели! К сожалению, отправка EOF невозможна, поскольку я бы хотел, чтобы сетевой поток продолжался бесконечно. Вы упомянули, что gawk будет ограничивать, когда происходит совпадение с регулярным выражением RS. Знаете ли вы, почему мое регулярное выражение не соответствует данным стандартного ввода? Я использую RS = "=|;";
. Я предполагаю, что это соответствует первому =
или ;
, а самый последний символ в моем стандартном вводе действительно =
. Поправьте меня, если я ошибаюсь, но я предполагаю, что это приведет к совпадению, хотя стандартный ввод все еще открыт? Спасибо!
Я понимаю. У нас есть завершающий разделитель записей =
, так почему же он зависает без обработки последней записи, которая четко разделена? Я подозреваю, что механизм регулярных выражений в Gawk работает с функцией просмотра вперед, хотя это конкретное регулярное выражение не требует этого.
Мне не совсем понятно, почему оно зависает, но после дальнейшей отладки у меня возникли подозрения по двум пунктам: 1) Как вы сказали, механизм регулярных выражений пытается прочитать больше данных, чтобы завершить регулярное выражение, которое будет блокироваться, несмотря на мой регулярное выражение, не требующее этого чтения. 2) Какая-то проблема с буферизацией. Я заметил, что если я ввожу =AAAA=AAAA;AAAA;AAAA=AA
(23 символа), он зависает... но если я добавляю еще один A
(сделав его 24 символа и пересекая границу в 4 байта)....тогда все работает правильно и производит желаемый результат. Очень странно. Я использую macOS. Возможно, есть какие-нибудь предложения? Огромное спасибо!
Да, это регулярное выражение не требует чтения, но регулярным выражениям, как правило, необходимо сканировать больше символов, несмотря на достижение соответствующего состояния в их конечном автомате. Кажется, это стоит исправить в моей реализации. Мы можем вычислить свойство maxmatchlen
во время компиляции регулярного выражения, что делает его доступным во время выполнения.
Очень интересно! Большое спасибо за подробное объяснение! Интересно, что вы думаете о ситуации с персонажами 23/24 из моего комментария выше? Считаете ли вы, что это артефакт механизма регулярных выражений, говорящий, что он прочитал достаточно символов, чтобы принять решение? ......или, возможно, это скорее проблема с буферизацией? Наверное, я просто с подозрением отношусь к буферизации, потому что она совпадает с пересечением 4-байтовой границы. Спасибо!
Вдобавок к этому я также заметил, что... если мое регулярное выражение имеет длину 5 символов... то мои входные данные должны иметь не менее 5 символов после самого последнего разделителя, чтобы оно выдавало правильные результаты. Если после моего ввода менее 5 символов, он будет выполнять такое поведение усечения. Аналогично, если я корректирую длину строки регулярного выражения, это также регулирует количество символов, необходимых после самого последнего разделителя, чтобы вызвать это поведение. Учитывая это, это, похоже, указывает на артефакт механизма регулярных выражений.
Я не вижу поведения с 23 по сравнению с 24 в реализации макроса TXR Lisp awk. Как только добавляется только один A
, предыдущая запись ограничивается, мы видим их четыре. Похоже, что регулярное выражение Gawk имеет дополнительное чтение и буферизацию.
Я исправил это в своем местном неизданном TXR. Теперь в тестовом примере я получаю четыре ряда AAAA
; до сих пор он все ограничивает и после этого зависает для ввода дополнительных данных. Мне нужно решить, стоит ли объединять и публиковать. Это потенциально полезный интерактивный ввод. Допустим, у нас есть TTY в необработанном режиме, а входные данные разделены CR-LF. read-until-match
с регулярным выражением, соответствующим CR-LF, будет вести себя правильно; он вернет строку, когда будет замечен CR-LF, не дожидаясь дальнейших действий.
Multiple Line (Руководство пользователя GNU Awk) говорит, что
RS == any single character
Записи разделяются по каждому вхождению символа. Несколько последовательных вхождений ограничивают пустые записи. (...)
RS == regexp
Записи разделяются по вхождению символов, соответствующих регулярному выражению. Пустые записи разделяются ведущими и конечными совпадениями регулярного выражения.(...)
Обратите внимание, что начальный и конечный значения упоминаются только для последнего, поэтому я подозреваю, что источником проблем может быть то, как это реализовано в GNU AWK
.
Если вам не нужно различать =
и ;
, я предлагаю следующий обходной путь.
cat -u example.txt - | sed -u 'y/;/=/' | gawk '
BEGIN {
RS = " = ";
}
{
if ($0 != "") {
print $0;
fflush();
}
}
'
что для example.txt
контента
=AAAA=AAAA;AAAA;AAAA=
дает результат
AAAA
AAAA
AAAA
AAAA
и висит. Объяснение: я добавил GNU sed
, работающий в небуферизованном режиме (-u
) с помощью одной команды y, которая делает
Транслитерировать любые символы в пространстве шаблонов, которые соответствуют любому из исходные символы с соответствующим символом в dest-chars.
При этом ;
заменяется использованием =
. Затем заменил RS
в команде gawk
на односимвольную строку =
.
(проверено в GNU sed 4.8 и GNU Awk 5.1.0)
Спасибо за информацию! К сожалению, мне нужно было бы различать два разделителя, поскольку мой сложный скрипт тела gawk должен обрабатывать эту часть. Есть ли потенциальные предложения, учитывая это ограничение? Спасибо!
В своем ответе я улучшил приведенное выше решение для распознавания разделителей.
Обходной путь, включающий цикл чтения оболочки в конвейер для разделения исходного ввода awk (фактического вывода netcat
OP) на отдельные символы, а затем подачи их в awk по одному:
cat example.txt - |
while IFS= read -r -d '' -N1 char; do printf '%s\0' "$char"; done |
awk -v RS='\0' '
/[;=]/ { if (rec != "") { print rec; fflush() }; rec = ""; next }
{ rec=rec $0 }
'
AAAA
AAAA
AAAA
AAAA
Для этого требуется GNU awk или какой-либо другой, который может обрабатывать символ NUL
как RS
, поскольку это поведение, отличное от POSIX. Предполагается, что ваш ввод не может содержать NUL-байты, т. е. это действительный текстовый «файл» POSIX.
Если интересно, читайте, как мы туда попали...
Я подумал, что здесь есть как минимум одна ошибка, поскольку обнаружил множество странностей (см. ниже), поэтому открыл отчет об ошибке на странице https://lists.gnu.org/archive/html/bug-gawk/2024-07/ msg00006.html но, по мнению провайдера gawk, Арнольда, различия в поведении в этом случае — это всего лишь детали реализации, требующие предварительного чтения, чтобы убедиться, что регулярное выражение соответствует правильной строке.
Кажется, здесь есть 3 проблемы, например. использование GNU awk 5.3.0 в Cygwin:
$ printf 'A;B;C;\n' > file
$ cat file - | awk -v RS='(;|=)' '{print NR, $0}'
1 A
$ cat file - | awk -v RS=';|=' '{print NR, $0}'
1 A
2 B
$ cat file - | awk -v RS='[;=]' '{print NR, $0}'
1 A
2 B
3 C
(;|=)
, ;|=
и [;=]
должны быть эквивалентны, но в данном случае это явно не так.
Хорошей новостью является то, что вы, очевидно, можете обойти эту проблему, используя выражение в скобках, как в третьем случае выше, вместо «или».
;
:$ printf 'A;B;C;' > file
$ cat file - | awk -v RS='(;|=)' '{print $0; fflush()}'
$ cat file - | awk -v RS=';|=' '{print $0; fflush()}'
A
$ cat file - | awk -v RS='[;=]' '{print $0; fflush()}'
A
B
Плохая новость заключается в том, что это влияет на пример OP:
$ printf ';AAAA;BBBB;CCCC;DDDD;' > file
С буквенным символом RS:
$ cat file - | awk -v RS=';' '{print $0; fflush()}'
AAAA
BBBB
CCCC
DDDD
С регулярным выражением RS, которое также должно сделать этот символьный литерал:
$ cat file - | awk -v RS='[;]' '{print $0; fflush()}'
AAAA
BBBB
CCCC
$ printf ';AAAA;BBBB;CCCC;DDDD;x' > file
$ cat file - | awk -v RS='[;]' '{print $0; fflush()}'
AAAA
BBBB
CCCC
DDDD
$ printf 'A;B;C;\n' > file
$ cat file - | awk -v RS='[;|=]' '{print $0; fflush()}'
A
$ cat file - | awk -v RS='[;a=]' '{print $0; fflush()}'
A
B
C
Кстати, я попробовал установить тайм-аут:
$ cat file - | awk -v RS='[;]' 'BEGIN{PROCINFO["-", "READ_TIMEOUT"]=100} {print $0; fflush()}'
A
B
awk: cmd. line:1: (FILENAME=- FNR=3) fatal: error reading input file `-': Connection timed out
$ cat file - | awk -v RS='[;]' -v GAWK_READ_TIMEOUT=1 '{print $0; fflush()}'
A
B
и stdbuf для отключения буферизации:
$ cat file - | stdbuf -i0 -o0 -e0 awk -v RS='[;]' '{print $0; fflush()}'
A
B
и сопоставляем каждый символ (думая, что затем можно использовать RT ~ /[=;]/
, чтобы найти разделитель):
$ cat file - | awk -v RS='(.)' '{print RT; fflush()}'
A
;
B
;
C
но ни один из них не позволил мне прочитать последний разделитель записей, поэтому на данный момент я не знаю, что может сделать ОП, чтобы успешно прочитать последнюю запись продолжающегося ввода, используя регулярное выражение, отличное от чего-то вроде этого:
$ printf 'A;B;C;' > file
$ cat file - |
while IFS= read -r -d '' -N1 char; do printf '%s\0' "$char"; done |
awk -v RS='\0' '/[;=]/ { print rec; fflush(); rec = ""; next } { rec=rec $0 }'
A
B
C
и использование образца ввода OP, но с разным текстом для каждой записи, чтобы сделать сопоставление входных и выходных записей более понятным:
$ printf '=AAAA=BBBB;CCCC;DDDD=' > example.txt
$ cat example.txt - |
while IFS= read -r -d '' -N1 char; do printf '%s\0' "$char"; done |
awk -v RS='\0' '/[;=]/ { print rec; fflush(); rec = ""; next } { rec=rec $0 }'
AAAA
BBBB
CCCC
DDDD
Мы используем символы NUL в качестве разделителей и различные параметры, указанные выше, чтобы сделать цикл чтения оболочки достаточно надежным для обработки пустых строк и других пробелов во входных данных, см. https://unix.stackexchange.com/a/49585/ 133219 и https://unix.stackexchange.com/a/169765/133219 для получения подробной информации по этим вопросам. Мы дополнительно используем NUL-символ для awk RS, чтобы он мог различать символы новой строки, поступающие из исходного ввода, и символ новой строки, добавляемый оболочкой printf
, в противном случае rec
в сценарии awk никогда не сможет содержать новую строку, поскольку они ВСЕ будет использовано при сопоставлении RS по умолчанию.
Мы используем канал в/из цикла while-read вместо подстановки процесса, просто чтобы внести ясность, поскольку OP уже использует каналы.
Ставить \n
в файле — это обман. ;-)
@Armali, для проблемы трех предположительно эквивалентных регулярных выражений, дающих разные результаты, нет никакой разницы, есть ли \n
или нет. Я поместил его туда, чтобы исключить возможность того, что его отсутствие (и поэтому входные данные не являются допустимым текстовым файлом POSIX) вызывает проблему.
Даже третий случай не работает без \n
(GNU Awk 4.1.4).
@Armali, получите более новую версию gawk, она устарела на 8 лет, и сейчас мы используем gawk 5.3.0, в промежутке было исправлено несколько ошибок и улучшено.
Надеюсь, ОП заметил ваше замечание The good news is you can apparently work around that problem using a bracket expression as in the 3rd case above instead of an "or".
. Он/она может использовать RS = "[=;]";
как временное решение (и добавить в код комментарий с ограничениями). Вы можете начать свой ответ с выделения этого альтернативного решения перед остальным объяснением.
@WalterA, хотя это решает одну проблему, это не решит всю проблему ОП, поскольку элемент «2» из моего списка все равно будет существовать, поэтому они все равно не увидят последнюю запись из входных данных, если не сделают то, что я показываю вверху моего ответа.
Комбинация решений @daweo и @EdMorton:
ОП хочет иметь логику, основанную на распознавании двух разделителей, и, возможно, захочет использовать для этого RT.
Сначала используйте обходной путь Эда для чтения входных данных по одному символу за раз.
Когда =
найден, добавьте ;
в качестве разделителя.
В awk
исправьте RT, если =
является частью строки.
РТ распечатаю после печати $0
.
cat example.txt - |
while IFS= read -r -d '' -N1 char; do
if [[ "$char" == '=' ]]; then
printf "=;"
else
printf '%s' "$char"
fi
done | awk '
BEGIN {
RS = ";"
}
/=/ {
RT = " = ";
sub(/=/,"", $0)
}
{
if ($0 != "") {
print $0 "(RT = " RT ")";
fflush();
}
}
'
Результат:
AAAA(RT==)
AAAA(RT=;)
AAAA(RT=;)
AAAA(RT==)
Хорошая идея; тем не менее, я бы использовал Perl вместо Bash +1
что-то вроде perl -npe 'BEGIN{$/ = \1; $| = 1} $_ .= ";" if $_ eq " = "'
Решение, которое не требует изменения сценария awk: поскольку он игнорирует пустые записи, мы можем просто дублировать каждый разделитель записей на этапе конвейера, вставленный перед gawk
, например. г.
python -c '
import os
for i in iter(lambda: os.read(0, 1), b""):
os.write(1, i)
if i in b"=;": os.write(1, i)
' |
Это не решает полностью проблему с awk; по крайней мере -v RS=';|='
все равно не сработает
Если настройка RS рассматривается как часть сценария, вы правы; в этом случае следует использовать менее требовательный «[=;]» или реплицировать разделитель записей по мере необходимости для упреждающего чтения регулярного выражения, например. г. четыре раза.
Я вообще не смог повторить это с любым имеющимся у меня вариантом awk
:
gawk -c
и gawk -P
выглядят неуместно по дизайну. for __ in 'mawk1' 'mawk2' 'nawk' 'gawk -e' 'gawk -be' \
'gawk -ce' 'gawk -Pe' 'gawk -Mbe' 'gawk -nbe'; do
( time ( timeout --foreground 10
echo '=AAAA;=BBBB;=CCCC;=DDDD;' | $( printf '%s' "$__" ) '
BEGIN { RS = "[\n=;]+"
OFS = "\3"
} {
print NR, FNR, NR, length(),
"$0 := \""($0)"\"",
"$1 := \""($1)"\"",
"$NF := \""($NF)"\"" }' ) | gcat - ) |
column -s$'\3' -t
echo "\f\t$__ done ...\n"
done
( timeout --foreground 10 echo '=AAAA;=BBBB;=CCCC;=DDDD;' | ; )
0.00s user 0.01s system 110% cpu 0.011 total
gcat - 0.00s user 0.00s system 39% cpu 0.010 total
1 1 1 0 $0 := "" $1 := "" $NF := ""
2 2 2 4 $0 := "AAAA" $1 := "AAAA" $NF := "AAAA"
3 3 3 4 $0 := "BBBB" $1 := "BBBB" $NF := "BBBB"
4 4 4 4 $0 := "CCCC" $1 := "CCCC" $NF := "CCCC"
5 5 5 4 $0 := "DDDD" $1 := "DDDD" $NF := "DDDD"
mawk1 done ...
( timeout --foreground 10 echo '=AAAA;=BBBB;=CCCC;=DDDD;' | ; )
0.00s user 0.01s system 127% cpu 0.008 total
gcat - 0.00s user 0.00s system 38% cpu 0.007 total
1 1 1 0 $0 := "" $1 := "" $NF := ""
2 2 2 4 $0 := "AAAA" $1 := "AAAA" $NF := "AAAA"
3 3 3 4 $0 := "BBBB" $1 := "BBBB" $NF := "BBBB"
4 4 4 4 $0 := "CCCC" $1 := "CCCC" $NF := "CCCC"
5 5 5 4 $0 := "DDDD" $1 := "DDDD" $NF := "DDDD"
mawk2 done ...
( timeout --foreground 10 echo '=AAAA;=BBBB;=CCCC;=DDDD;' | ; )
0.00s user 0.01s system 112% cpu 0.007 total
gcat - 0.00s user 0.00s system 31% cpu 0.006 total
1 1 1 0 $0 := "" $1 := "" $NF := ""
2 2 2 4 $0 := "AAAA" $1 := "AAAA" $NF := "AAAA"
3 3 3 4 $0 := "BBBB" $1 := "BBBB" $NF := "BBBB"
4 4 4 4 $0 := "CCCC" $1 := "CCCC" $NF := "CCCC"
5 5 5 4 $0 := "DDDD" $1 := "DDDD" $NF := "DDDD"
nawk done ...
( timeout --foreground 10 echo '=AAAA;=BBBB;=CCCC;=DDDD;' | ; )
0.00s user 0.01s system 61% cpu 0.018 total
gcat - 0.00s user 0.00s system 10% cpu 0.017 total
1 1 1 0 $0 := "" $1 := "" $NF := ""
2 2 2 4 $0 := "AAAA" $1 := "AAAA" $NF := "AAAA"
3 3 3 4 $0 := "BBBB" $1 := "BBBB" $NF := "BBBB"
4 4 4 4 $0 := "CCCC" $1 := "CCCC" $NF := "CCCC"
5 5 5 4 $0 := "DDDD" $1 := "DDDD" $NF := "DDDD"
gawk -e done ...
( timeout --foreground 10 echo '=AAAA;=BBBB;=CCCC;=DDDD;' | ; )
0.00s user 0.00s system 106% cpu 0.008 total
gcat - 0.00s user 0.00s system 21% cpu 0.008 total
1 1 1 0 $0 := "" $1 := "" $NF := ""
2 2 2 4 $0 := "AAAA" $1 := "AAAA" $NF := "AAAA"
3 3 3 4 $0 := "BBBB" $1 := "BBBB" $NF := "BBBB"
4 4 4 4 $0 := "CCCC" $1 := "CCCC" $NF := "CCCC"
5 5 5 4 $0 := "DDDD" $1 := "DDDD" $NF := "DDDD"
gawk -be done ...
( timeout --foreground 10 echo '=AAAA;=BBBB;=CCCC;=DDDD;' | ; )
0.00s user 0.00s system 104% cpu 0.008 total
gcat - 0.00s user 0.00s system 19% cpu 0.007 total
1 1 1
25 $0 := "=AAAA;=BBBB;=CCCC;=DDDD;"
$1 := "=AAAA;=BBBB;=CCCC;=DDDD;"
$NF := "=AAAA;=BBBB;=CCCC;=DDDD;"
gawk -ce done ...
( timeout --foreground 10 echo '=AAAA;=BBBB;=CCCC;=DDDD;' | ; )
0.00s user 0.00s system 108% cpu 0.007 total
gcat - 0.00s user 0.00s system 21% cpu 0.007 total
1 1 1
25 $0 := "=AAAA;=BBBB;=CCCC;=DDDD;"
$1 := "=AAAA;=BBBB;=CCCC;=DDDD;"
$NF := "=AAAA;=BBBB;=CCCC;=DDDD;"
gawk -Pe done ...
( timeout --foreground 10 echo '=AAAA;=BBBB;=CCCC;=DDDD;' | ; )
0.00s user 0.00s system 79% cpu 0.011 total
gcat - 0.00s user 0.00s system 13% cpu 0.010 total
1 1 1 0 $0 := "" $1 := "" $NF := ""
2 2 2 4 $0 := "AAAA" $1 := "AAAA" $NF := "AAAA"
3 3 3 4 $0 := "BBBB" $1 := "BBBB" $NF := "BBBB"
4 4 4 4 $0 := "CCCC" $1 := "CCCC" $NF := "CCCC"
5 5 5 4 $0 := "DDDD" $1 := "DDDD" $NF := "DDDD"
gawk -Mbe done ...
( timeout --foreground 10 echo '=AAAA;=BBBB;=CCCC;=DDDD;' | ; )
0.00s user 0.00s system 108% cpu 0.007 total
gcat - 0.00s user 0.00s system 23% cpu 0.007 total
1 1 1 0 $0 := "" $1 := "" $NF := ""
2 2 2 4 $0 := "AAAA" $1 := "AAAA" $NF := "AAAA"
3 3 3 4 $0 := "BBBB" $1 := "BBBB" $NF := "BBBB"
4 4 4 4 $0 := "CCCC" $1 := "CCCC" $NF := "CCCC"
5 5 5 4 $0 := "DDDD" $1 := "DDDD" $NF := "DDDD"
gawk -nbe done ...
Один из провайдеров gawk, Энди Шорр, по какой-то причине не смог создать учетную запись Stackoverflow, поэтому попросил меня опубликовать для него его предложение (см. https://lists.gnu.org/archive/html/bug-gawk). /2024-07/msg00012.html для первоисточника):
От Энди:
Рассматривали ли вы попытку использовать расширение select и его неблокирующая функция?
Кажется, что-то вроде этого работает:
(echo "A;B;C;D;"; cat -) | gawk -v 'RS=[;=]' -lselect -ltime '
BEGIN {
fd = input_fd("")
set_non_blocking(fd)
PROCINFO[FILENAME, "RETRY"] = 1
while (1) {
delete readfds
readfds[fd] = ""
select(readfds, writefds, exceptfds)
while ((rc = getline x) > 0) {
if (rc > 0)
printf "%d [%s]\n", ++n, x
else if (rc != 2) {
print "Error: non-retry error"
exit 1
}
}
}
}'
Если во входном потоке нет символов новой строки, вы можете попробовать
gawk -v FPAT='[^;=]+' '{ for(i = 1; i <= NF; i++) { print $i; fflush() }}'
.