Возможно ли, с `sed` или иначе, применить преобразование регулярных выражений к входному потоку, который не может содержать строки новой строки?

У меня есть конвейер оболочки, содержащий выражение sed:

source | sed -e 's:xxxxx:yyyyy:g' | sink 

Он работает, но имеет скрытый недостаток, потому что sed работает на целых линиях. Это означает, что sink ничего не увидит, пока source отправит новую строку. Это становится проблемой, если источник никогда не отправляет новую строку.

Оболочка bash но я надеюсь, что это не имеет значения. Строки xxxxx и yyyyy могут быть любым регулярным выражением и могут использовать группы захвата, чтобы скопировать некоторые x в y .

Возможно ли, с sed или иначе, применить преобразование регулярных выражений к входному потоку, который не может содержать новые строки?

Я решил проблему, написав фильтр в Ruby, но мне интересно, можно ли использовать существующие инструменты вместо написания кода.

4 Solutions collect form web for “Возможно ли, с `sed` или иначе, применить преобразование регулярных выражений к входному потоку, который не может содержать строки новой строки?”

Собственно, думая об этом, вы можете сначала затронуть все возможности:

 source | tr '\n<' '<\n' | paste -sd\\n - -| sed -e'/^[0-9]\{1,\}>/!{$!H;1h;$!d'\ -e\} -e'x;y/\n</<\n/;s//<&/' \ -ew\ /dev/fd/1 | filter ... | sink 

Это приведет к поломке вашего потока в первом случае без предварительной замены всех < for \n и последующей условной замены их обратно соответствующим образом. Это необходимо, потому что ограничитель, о котором вы упоминаете, не является одним символом (в качестве новой строки), и поэтому простого перевода недостаточно, чтобы гарантировать ваш контекст редактирования, если вы не разделите свой поток. Другими словами, требуемые изменения, которые могут потребоваться, такие как группы захвата и другие контекстно-зависимые соответствия, которые, как понимается, применяются к записи, нельзя надежно сделать, пока вы не проверите свои конечные точки.


небуферизован


sed только буферизует до первого появления во вводе соответствия регулярному выражению <[0-9]+> , сначала переводя все < to \n ewlines и наоборот, а затем складывая входные данные по строке в старом пространстве sed до ^[0-9]\{1,\}> соответствует.

Но tr и paste делают вывод блока буфера при записи в каналы – 4kb блоков или более.

Еще две версии, которые обрабатывают:

 sol1(){ { cat; printf '\n\4\n'; } | { dd obs=1 cbs=512 conv=sync,unblock \ <"$(pts;stty eol \";cat <&3 >&4&)" } 3<&0 <&- <>/dev/ptmx 2>/dev/null 4>&0 | sed -ne'/<[0-9]\{1,\}>/!{H;$!d' -e\} \ -e'x;s/\n//g;w /dev/fd/1' } 

Это подталкивает все входные данные к pty и устанавливает dd для чтения из него. Он использует эту небольшую программу C из вашего другого вопроса – pts – для разблокировки и назначения dd fd. В приведенном выше случае это ядро, которое делает разграничение. Дисциплина pty line настраивается как " как stty eol char», которая не удаляется из вывода, поскольку символ eof делает, но делает push-буфер для dd для каждого события и удовлетворяет его read() . Для каждого из dd 's read() s сначала вставляет конец своего out-buffer в 512 символов с пробелами, а затем сжимает любые / все конечные пробелы до одной новой строки.

Вот модифицированная версия, которая может решить проблему с последней строкой:

 sol1_5(){ { cat; printf '\n\4\n'; } | { dd ibs=16k obs=2 cbs=4k conv=sync,unblock <"$(pts stty raw isig quit \^- susp \^- min 1 time 2 cat <&3 >&4&)" } 3<&0 <&- <>/dev/ptmx 2>/dev/null 4>&0 | sed -ne's/<[0-9]\{1,\}>/\n&/g;/./w /dev/fd/1' } 

Вот еще одна, совсем другая версия, которая отключает tr и paste :

 sol2(){ stdbuf -o0 tr '\n<' '<\n' | stdbuf -o0 paste -sd\\n - -| sed -ue'/^[0-9]\{1,\}>/!{$!H;1h;$!d'\ -e\} -e'x;y/\n</<\n/;s//<&/' } 

Я тестировал оба метода с вашими примерными данными:

 for sol in 1 2 do printf '<37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)"<37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)"<37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)"<37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)"' | cat - /dev/tty | "sol$sol" | cat 

В обоих случаях первые три строки печатались сразу, но четвертый был спрятан в буфере – потому что sed не печатает буфер до тех пор, пока не найдет начало следующего, и поэтому он остается одной строкой за входом до EOF. Нажатие CTRL+D напечатало его.


 <37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)" <37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)" <37> Jul 28 10:40:47 127.0.0.1 time="2015-07-28 10:40:47" msg="LOGOUT User admin logged out on TELNET (10.0.200.1)" <37> Jul 28 10:45:58 127.0.0.1 time="2015-07-28 10:45:58" msg="LOGIN User admin logged in on TELNET (10.0.200.1)" 

Но sol1_5 использует другой метод вообще – он не полагается на контекст символа, чтобы разграничить ввод, но вместо этого полагает, что каждая write() из 4k или меньше байтов должна представлять как минимум 1 полный контекст, и поэтому он добавляет новые строки внутри каждого, поскольку он считает нужным и немедленно сбрасывает выходной сигнал.

Он работает, устанавливая значения stty min и time для dd . Если вы установите min > 0 и time > 0 на неканоническом терминальном устройстве, этот терминал будет блокировать чтение до тех пор, пока он не получит хотя бы min символы, а затем продолжит блокироваться до тех time пока не пройдут десятые доли секунды. Таким образом, если вы можете полагаться на каждую write() чтобы на терминале было так много байтов и заканчивалось за такое время – и я думаю, что 4k и .2 secs довольно справедливые предположения для записи журнала лично – тогда вы можете читать ввод и синхронно вывести поток.

И поэтому sol1_5 напечатал все 4 строки.


сценарий sed


На самом деле это очень простая методология и, вероятно, может быть адаптирована довольно универсально для обработки множественных разделителей символов с помощью sed – которая по умолчанию ограничивает только записи на одном символе – новой строке.

  1. Преобразуйте все вхождения первого символа в шаблоне разделителя в новую строку и любые возникающие символы новой строки для этого символа.

    • Часть отмеченного осложнения ниже: убедитесь, что у вас есть новые строки в конце вашего потока.

    • tr '\n<' '<\n' | paste -sd\\n - -

  2. Сканирование нового ввода с разделителем новой строки для остальной части шаблона разделителя – но только для случаев в начале строки.

    • Помимо простоты, это также очень эффективно. Вам нужно только проверить первые несколько символов для любой строки ввода. sed едва ли должен работать вообще.

    • /^[0-9]\{1,\}>/

  3. Приложите к H старое пространство копию любой строки, которая делает ! не совпадают и d elete it, но для тех, кто это делает, e x изменяет редактирование и удерживает буферы, поэтому ваше текущее пространство шаблонов – это все последние полностью разделенные записи, а пространство для удержания содержит только самую первую часть следующей разделительной последовательности.

    • Самый сложный бит – это то, что вам нужно позаботиться о первой и последней строках ввода. Усложнение здесь связано с базовой эффективностью sed – вы действительно можете работать с одной записью на буфер.

    • Вы не хотите вставлять лишний \n ewline без всякой причины в первой строке, поэтому вы должны перезаписать h старое пространство, а не добавлять к нему старое пространство в этом случае.

    • И вы оба должны ! не H old или d elete $ last line, потому что он пуст, но ваш буфер удержания не является. Больше нет ввода для сканирования, но вам все равно придется обрабатывать последнюю записанную запись.

    • /.../!{$!H;1h;$!d;};x

  4. Вместо того, чтобы применять дорогостоящее regexp s/// ubstitution regexp для восстановления вашего теперь полностью разделенного контекста, вы можете вместо этого использовать собственную функцию y/// транслитерации sed для более эффективного и в одно время сменить все сохраненные промежуточные \n ewline символов для вашего первого символа разделителя.

    • y/\n</<\n/
  5. Наконец, вам просто нужно вставить один новый < во главе пространства шаблонов – потому что введенная \n строка была добавлена ​​в конце последнего цикла буфера при ее печати.

    • Самый эффективный способ сделать это – это просто повторное использование // же регулярного выражения, с которым вы все время тестировали свои строки ввода. Таким образом, sed может уйти с необходимостью только для regcomp() скомпилировать одно регулярное выражение, и повторно regexec() выполняет один и тот же автомат несколько раз, чтобы обеспечить regcomp() всего потока в потоке.

    • s//<&/

Вы должны иметь возможность обрабатывать этот поток в виде обычного текстового файла с ограниченным текстом.

контрольная работа

 printf '%s\n' the string \ "<9>more $(printf %050s|tr ' ' '<') string" \ and \<9\> "<more<string and <9> more<string" | tr '<\n' '\n<' | paste -sd\\n - - | sed -e'/^[0-9]\{1,\}>/!{$!H;1h;$!d' \ -e\} -e'x;y/\n</<\n/;s//<&/' 

 the string <9>more <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< string and <9> <more<string and <9> more<string 

Теперь, если ваша цель – применить ваше редактирование к строке, которая может быть описана как ^<[0-9]+>(^(<[0-9]+>))* то на этом этапе вы, вероятно, даже не нужен второй фильтр, потому что это именно то, что представляет собой пространство шаблонов sed прежде чем оно распечатает его в конце маленького скрипта там – \n ewlines и все.

Повторно используя модифицированную версию моего предыдущего примера …

строка> данные

 printf '%s\n' the string \ "<1>more $(printf %050s|tr ' ' '<') string" \ and \<2\> "<more<string and <3> more<string" | tr '<\n' '\n<' | paste -sd\\n - - | sed -e'/^[0-9]\{1,\}>/!{$!H;1h;$!d' \ -e\} -e'x;y/\n</<\n/;s//<&/' \ -e'/^<[2-9][0-9]*>/s/string/data/g' 

 the string <1>more <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< string and <2> <more<data and <3> more<data 

Когда программа записывает на терминал, буфер заливается на каждую новую строку, но вы можете использовать программу unbuffer (Примечание в некоторых дистрибутивах команда stdbuf)

Попробуйте что-то вроде этого

 unbuffer source | sed -e 's:xxxxx:yyyyy:g' | sink 

Я реализовал небольшой скрипт в Ruby для решения этой проблемы. Он используется следующим образом:

 source | myscript.rb | sink 

Вот источник

 $stdout.sync # no outbound buffering by Ruby buf='' $stdin.each_char do |c| if buf.length>0 || c=='<' # buffering starts when '<' received buf << c # and continues until flushed buf.gsub!(/(<\d+>)/,"\n\\1") if (c == '>') # regex transform matching buffer unless (buf =~ /<\d*$/) # flush buffer when regex fails STDOUT << buf buf.replace '' # empty buffer stops buffering end else STDOUT << c; # unbuffered output end $stdout.flush # no buffering, please! end 

Эксперт по рубинам может улучшить это, но это быстрый «грязный» взлом, который решает проблему для меня.

В принципе, он читает stdin по одному символу за раз и проверяет его для первого символа соответствия, < . Если он не найден, он немедленно записывает его в stdout. Если он совпал, он вместо этого записывается в буфер и продолжает это делать, если содержимое буфера не приведет к совпадению регулярных выражений для допустимого разделителя ( < последовали ноль или более цифр), и в этом случае он очищает буфер и буферизацию останавливается. Если при буферизации он получает a > то он выполняет преобразование с помощью регулярного выражения.

Обновить

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

 STDOUT.sync # no outbound buffering by Ruby buf='' def read_from_stdin() last='' while true begin c = STDIN.read_nonblock(1) # read 1 character; don't block rescue Errno::EWOULDBLOCK # exception if nothing to read yield "\n" unless last=="\n" # send a newline if prior character wasn't IO.select([STDIN]) # block (wait for input) retry # go back to 'begin' again end yield last=c # remember and send read character end end read_from_stdin do |c| if buf.length>0 || c=='<' # buffering starts when '<' received buf << c # and continues until flushed buf.gsub!(/(<\d+>)/,"\n\\1") if (c == '>') # regex transform matching buffer unless (buf =~ /<\d*$/) # flush buffer when regex fails STDOUT << buf buf.replace '' # empty buffer stops buffering end else STDOUT << c; # unbuffered output end STDOUT.flush # no buffering, please! end 

По крайней мере, GNU sed обрабатывает ввод, у которого нет конца новой строки (и он выводит результат без окончательной новой строки, если последняя незавершенная строка передается). Текстовый файл под Unix должен по определению заканчиваться символом новой строки, если он не пуст, а sed – текстовая утилита, поэтому эта снисходительность к нетекстовому вводу не гарантируется.

Поскольку sed предназначен для преобразования строк, я ожидал бы, что большинство реализаций прочитают всю строку ввода в памяти до применения любого преобразования, и прежде всего, до создания любого выхода, соответствующего этой строке ввода.

Если вы хотите обработать этот ввод удобно с помощью sed, выберите символ, который не соответствует шаблону xxxxx или создан с помощью текста замены yyyyy , но достаточно часто появляется на входе. Переведите его на новую строку, и наоборот после вызова sed.

  source | tr ':\n' '\n:' | sed -e 's:foo:bar:g' | tr ':\n' '\n:' | sink 

Если у вас нет хорошего персонажа, sed, вероятно, не поможет вам здесь, а рубин – разумный выбор.

  • Хотите, чтобы rsync некоторые файлы после того, как они были sedded
  • метки тега sed
  • Использование sed для консолидации разностного вывода
  • Сгенерируйте порядковый номер для каждой строки в конце. Изменено. Может ли кто-нибудь помочь и предложить на этом
  • Как заменить определенную строку с помощью sed или awk на основе команды run
  • Повторяйте каждую строку несколько раз
  • Источник «gsed: не может читать: нет такого файла или каталога»?
  • Лучший способ запустить серию из семи одинаковых команд в sed?
  • Использовать sed для исправления сломанных строк новой строки
  • заменить содержимое одного поля на основе содержимого в другом поле
  • Необходимо вставить одинарные кавычки в текстовый файл для использования в качестве SQL-запроса с помощью sed
  • Linux и Unix - лучшая ОС в мире.