резервное копирование моего сервера debian на mycloud ex2 каждый час

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

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


unbuffered


sed буферизирует только до первого появления во вводе совпадения регулярного выражения <[0-9]+> сначала переводя все < в \newlines и наоборот, а затем складывая ввод за строкой в sed в Hold 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


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

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

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

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

  2. Просканируйте новый ввод, разделенный новой строкой, на наличие остального шаблона разделителя - но только для вхождений в начало строки.

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

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

  3. Добавьте к Hold space копию любой строки, которая ! не совпадает и dудалите ее, а для тех, которые совпадают, exизмените буферы редактирования и удержания так, чтобы текущее пространство шаблона содержало всю последнюю полностью разделенную запись, а пространство удержания содержало только самую первую часть следующей разделенной последовательности.

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

    • Вы не хотите вставлять лишнюю \newline без причины в первую строку, поэтому в этом случае вам следует перезаписать hold space, а не добавлять к Hold space.

    • И вы должны оба ! не Hold или delete $ последнюю строку, потому что она пуста, а ваш буфер удержания - нет. Больше нет входных данных для сканирования, но вам все еще нужно обработать последнюю удерживаемую запись.

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

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

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

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

    • s//<&/

Теперь вы должны быть в состоянии обработать этот внешний поток как обычный, \newline-разграниченный текстовый файл.

test

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 перед тем, как вывести его в конце маленького скрипта - \newlines и все.

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

string>data

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
0
04.12.2017, 10:04
0 ответов

Теги

Похожие вопросы