На самом деле, подумав об этом, вы можете сначала воздействовать на все возможные варианты:
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
Это сломает ваш поток, сначала безусловно поменяв все <
на \n
, а затем условно поменяв их обратно соответствующим образом. Это необходимо, потому что разделитель, который вы упоминаете, не является отдельным символом (как новая строка) , и поэтому простого перевода недостаточно, чтобы гарантировать контекст вашей правки, если вы сначала не разделите ваш поток. Другими словами, правки, которые, как вы упоминаете, могут потребоваться - такие как группы захвата и другие контекстно-зависимые соответствия, которые, как подразумевается, применяются к записи - не могут быть сделаны надежно, пока вы не проверите ваши конечные точки.
sed
буферизирует только до первого появления во вводе совпадения регулярного выражения <[0-9]+>
сначала переводя все <
в \n
ewlines и наоборот, а затем складывая ввод за строкой в sed
в H
old space, пока не будет найдено ^[0-9]\{1,\}>
совпадение.
Но tr
и paste
блокируют вывод буфера при записи в pipes - блоки по 4 кб и более.
Еще две версии, которые справляются с этим:
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 настроена на "
как stty
eol
char - который не удаляется из вывода, как eof
char, но выталкивает буфер pty в dd
для каждого появления и удовлетворяет его read()
. Для каждого из dd
read()
s он сначала заполняет хвостовую часть своего out-буфера до 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
's pty. Если вы установите min > 0
и time > 0
на неканоническом терминальном устройстве, то терминал будет блокировать чтение, пока не получит по крайней мере min
символов, и после этого будет продолжать блокировать, пока не пройдет time
десятых долей секунды. Таким образом, если вы можете рассчитывать на то, что каждая запись()
на терминал будет содержать столько-то байт и завершится за столько-то времени - а я лично считаю, что 4k и .2 секунды вполне справедливые предположения для записи в журнал - тогда вы сможете читать входные данные и промывать выходные синхронно.
И так sol1_5
сразу же напечатал все 4 строки.
На самом деле это очень простая методика, и, вероятно, ее можно адаптировать достаточно универсально для работы с многосимвольными разделителями с помощью sed
- который по умолчанию разграничивает записи только по одному символу - новой строке.
Преобразуйте все случаи появления первого символа в шаблоне разделителя в новую строку и все последующие новые строки в эту строку.
Часть отмеченного ниже усложнения: убедитесь, что в конце вашего потока есть новые строки.
tr '\n<' '<\n' | paste -sd\\\n - - -
Просканируйте новый ввод, разделенный новой строкой, на наличие остального шаблона разделителя - но только для вхождений в начало строки.
Кроме того, что это просто, это еще и очень эффективно. Вам нужно проверить только первые несколько символов для любой строки ввода. sed
вообще почти не работает.
/^[0-9]\{1,\}>/
Добавьте к H
old space копию любой строки, которая !
не совпадает и d
удалите ее, а для тех, которые совпадают, ex
измените буферы редактирования и удержания так, чтобы текущее пространство шаблона содержало всю последнюю полностью разделенную запись, а пространство удержания содержало только самую первую часть следующей разделенной последовательности.
Самый сложный момент заключается в том, что вам нужно позаботиться о первой и последней строках ввода. Сложность здесь возникает из-за базовой эффективности sed
- вы действительно можете работать с одной записью в буфере.
Вы не хотите вставлять лишнюю \n
ewline без причины в первую строку, поэтому в этом случае вам следует перезаписать h
old space, а не добавлять к H
old space.
И вы должны оба !
не H
old или d
elete $
последнюю строку, потому что она пуста, а ваш буфер удержания - нет. Больше нет входных данных для сканирования, но вам все еще нужно обработать последнюю удерживаемую запись.
/.../!{$!H;1h;$! d;};x
Вместо того, чтобы применять дорогой s///
ubstitution regexp для восстановления вашего теперь полностью разграниченного контекста, вы можете вместо этого использовать собственную sed
функцию транслитерации y///
для более эффективной и единовременной замены всех ваших сохраненных промежуточных \n
ewline символов на первый символ вашего разделителя.
y/\n<\n/
В последнем случае вам просто нужно вставить один новый <
в начало пробела шаблона - потому что \n
ewline, который вам нужно вставить, уже был добавлен в конце последнего цикла буфера при печати.
Самый эффективный способ сделать это - просто повторно использовать //
тот же самый regexp, которым вы все это время проверяли свои входные строки. Таким образом, sed
может обойтись только regcomp()
компиляцией одного регулярного выражения и многократным regexec()
многократным выполнением одного и того же автомата для надежного разграничения всего потока.
s//<&/
Теперь вы должны быть в состоянии обработать этот внешний поток как обычный, \n
ewline-разграниченный текстовый файл.
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