2017-02-06 13 views
2

Начиная с версии 2.25, фиксируется "bug" in grep, так что для завершения выходных строк используются нулевые байты, а не символы новой строки. Это работает так хорошо, как просто для захвата и обработки многострочных grep-матчей (см. Пример)Как обрабатывать каждый результат - вместо строки команды grep (oz) (старше 2.25)

К сожалению, я придерживаюсь версии grep версии 2.20 на производстве. Это означает, что для обработки \ n завершенных лог-файлов вы не можете отличить grep-match от каждой отдельной строки вывода.

Поэтому мой вопрос:

Что является наиболее эффективным способом для обработки каждого результата -instead из line- команды Grep (OZ), когда вы застряли с версии старше 2.25?

(Примечание: это небольшой пример более сложного сценария, который необходимо обработать более после 10k больших логах по запросу, поэтому мои поиски «наиболее эффективным» решением)

Простой пример:

test.log

flag test1 
flag test2 
flag test3 
    test4 
    test5 
flag test6 

test7 

flag test8 

test.sh

#!/bin/bash 
#regex explained: 
#(?s)enable multiline pattern search 
#(flag) capturegroup with pattern indicating new entry 
#[[:blank:]] followed by a space 
#(.*?) capturegroup for the rest of the entry, non-greedy 
#(?=(?:\r\n|[\r\n])(flag)|\z) positive lookahead: 
# - stop when the next newline begins with flag 
# - OR if last entry is a match: proceed 'till end of entry 

regex_multiline="(?s)(flag)[[:blank:]](.*?)(?=(?:\r\n|[\r\n])(flag)|\z)" 
logfile="./test.log" 

test1(){ 
    #this works only with grep 2.25 or higher, 
    #which returns a NULL-byte delimiter after each capture 
    echo start 
    while IFS= read -r -d '' line ; do 
     printf '<test>%s</test>\n' "$line" 
    done < <(grep -Pzo $regex_multiline $logfile) 
    echo end 
} 

test2(){ 
    #I need this to work for each match, instead of each line 
    echo start 
    while IFS= read -r line ; do 
     printf '<test>%s</test>\n' "$line" 
    done < <(grep -Pzo $regex_multiline $logfile) 
    echo end 
} 

Test 1 приводит, что я хочу:

start 
<test>flag test1</test> 
<test>flag test2</test> 
<test>flag test3 
     test4 
     test5</test> 
<test>flag test6 

test7 
</test> 
<test>flag test8</test> 
end 

Тест 2 Результаты в

start 
<test>flag test1</test> 
<test>flag test2</test> 
<test>flag test3</test> 
<test>  test4</test> 
<test>  test5</test> 
<test>flag test6</test> 
<test></test> 
<test>test7</test> 
<test> </test> 
<test>flag test8</test> 
end 
+0

Test 2 терпит неудачу, потому что вы удалили нулевой ограничитель '-d ''' в 'read', но' grep' продолжает производить вывод, 'NULL' разделил – Inian

+0

Нет, тест 2 не с' -d '' ', потому что grep pre 2.25 делает * not * производят null-output, но \ n вместо этого. (как указано в ссылке в моем OP). Если вы попробуете Test1 с grep <2.25, вы просто получите 'start \ nend' – Asgair

+0

Вам разрешено использовать другие инструменты, или это нужно сделать с помощью grep? –

ответ

0

Я нашел решение. Я думаю, это немного взломанный, но это согласуется с версией grep версии 2.20 и выше. Хотя не используйте его с grep 2.25 и выше. Это комбинация grep с параметрами -zon: - z (обрабатывать вход как набор строк, каждый из которых заканчивается нулевым байтом) - o (печать только совпадающих (непустых) частей соответствующей строки) - n (Префикс каждой строки вывода с номером строки 1 в его входном файле.)

Эта комбинация выведет «1:» в начале каждого нового совпадения. Всегда. (не уверен, если это ошибка в Grep, или дизайн, но это имеет смысл с опциями -z и -o)

1:flag test1 
1:flag test2 
1:flag test3 
    test4 
    test5 
1:flag test6 

test7 

1:flag test8 

Итак, зная это, это приведет к следующей находке, и -replace, которая заменит каждую строку, начинающуюся с символа 1: с нулевым байтом. Обратите внимание, что в конце каждой строки ожидается нуль-байтовый символ, поэтому нам нужно добавить его вручную для последней строки!

Это может быть сделано с:

SED -e 'S/^ 1:/\ x0/г' | sed -e '$ a \ x0' или awk '{gsub (/^1: /, "\ x0");} 1' | СЕПГ -e «$ A \ x0»

(я думаю, что СЭД является более эффективным/быстрее для такого рода работы, но не придавить меня на это.)

test2(){ 
    #This finally works! 
    echo start 
    while IFS= read -r -d '' line ; do 
     printf '<test>%s</test>\n' "$line" 
    done < <(grep -Pzon $regex_multiline $logfile | sed -e 's/^1:/\x0/g' | sed -e '$a\\x0') 
    echo end 
} 
1

Я думаю, вы бы лучше с помощью perl вместо grep здесь. Вы можете использовать регулярное выражение почти неизмененной , только что подменяя \1\x00 :

regex_multiline="(?s)(flag[[:blank:]].*?)(?=(?:\r\n|[\r\n])flag|\z)" 
perl -0777 -pe "s/$regex_multiline/\1\x00/g" < "$logfile" 

Ваше регулярное выражение было немного странно, с capturegroups, что ничего не делать в контекст вашей команды grep (например, (flag)). Я просто положил всю часть, которую вы хотите сопоставить, в одну группу, чтобы она соответствовала \1 в заменяемой части. При необходимости отрегулируйте/Я что-то упустил.

Использование \1\0 (для «групповой матч один», «нулевой байт») на самом деле тоже работает, но это, кажется, своего рода запутанным.

+0

Верно, что группы захвата регулярных выражений не используются в grep, но в исходном скрипте одно и то же регулярное выражение повторно используется в 'while read и т. Д. ~ =" $ Regex_multiline "и' $ {BASH_REMATCH [1] } и т. Д. ' – Asgair

+0

Это имеет смысл. Помогло ли это решение для вас? –

+0

извините за задержку, но я определенно собираюсь проверить ваше решение, как только смогу. Я дам Вам знать. – Asgair