Подстановка команд в выражении sed

У меня небольшие проблемы с bash/sed. Мне нужно иметь возможность использовать подстановку команд в выражении sed. У меня есть два больших текстовых файла:

  • во-первых, это файл logfile.txt, который иногда* показывает сообщения об ошибках по идентификатору (0xdeadbeef — распространенный пример) в формате ERRORID:0xdeadbeef.

  • во втором файле errors.txt сообщения об ошибках хранятся парами LONG_ERROR_DESCRIPTION, 0xdeadbeef

Я пытался использовать sed с подстановкой команд bash для выполнения задачи:

cat logfile.txt | sed "s/ERRORID:\(0x[0-9a-f]*\)/ERROR:$(cat errors.txt |
    grep \1 | grep -o '^[A-Z_]*' )/g"

(^^^ это должно быть в одной строке, конечно)

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

   Lot's of meaningless stuff ERRORID:0xdeadbeef and something else =>
=> Lot's of meaningless stuff ERROR:LONG_ERROR_DESCRIPTION and something else 

Но это не так. Проблема в том, что sed не может «внедрить» раздел регулярного выражения (\1) в подстановку команд. Каковы мои другие варианты? Я знаю, что можно сначала построить выражение sed или сделать это другим способом, но я хотел бы избежать многократного разбора этих файлов (они могут быть огромными).

Как всегда большое спасибо за любую помощь.

* Внутри лог-файла нет реального форматирования. Разделы, столбцы, разделители табуляции/запятой не используются непоследовательно.

PS. Просто объяснить. Следующее выражение работает, но, конечно, в нем не передается аргумент:

echo "my cute cat" | sed "s/cat/$(echo dog)/g"

person yatsek    schedule 05.10.2011    source источник
comment
Дизайн errors.txt не совсем подходит для машинной обработки. Что-то вроде 0xdeadbeef Long description будет легче обрабатывать. Может быть, вы захотите сгенерировать из него кучу #define в другой прекрасный день?   -  person tripleee    schedule 06.10.2011
comment
@tripleee - на самом деле у меня есть куча ужасно нарезанных комментариями #define - и это мой реальный случай. Я пытался максимально ограничить этот пример, чтобы не вдаваться в подробности. Важным было просто иметь установку, подобную #define: TEXT 0xdeadbeef. Вот почему ваше решение также нуждается в некоторой настройке с моей стороны. Мне нужно собрать все заголовочные файлы и сгенерировать из них файл *.sed. Я работаю над этим, но ваш подход максимально близок к чему-то полезному.   -  person yatsek    schedule 06.10.2011


Ответы (4)


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

В общем, что-то в этом роде:

sed 's/\(.*\), 0x\([0-9A-F]*\)$/s%ERRORID:0x\2%ERROR:\1%g/' errors.txt |
sed -f - logfile.txt

Вывод первого скрипта sed должен быть примерно таким:

s%ERRORID:0x00000001%ERROR:Out of memory%
s%ERRORID:0x00000002%ERROR:Stack overflow%
s%ERRORID:0x00000031%ERROR:values of beta may cause dom%

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

Существуют разные диалекты sed, поэтому может потребоваться небольшая настройка. Я считаю, что sed в Linux должен использовать обратную косую черту перед группировкой скобок в регулярных выражениях и с радостью допускает стандартный ввод в качестве аргумента для параметра -f. Однако это не переносимо на другие Unices (но вы можете заменить Perl на sed, если вам нужна переносимость).

*Изменить. Если сообщения об ошибках довольно статичны и/или вы хотите прочитать журнал из стандартного ввода, сохраните сгенерированный сценарий в файле;

# Do this once
sed 's/\(.*\), 0x\([0-9A-F]*\)$/s%ERRORID:0x\2%ERROR:\1%g/' errors.txt >errors.sed
# Use it many times
sed -f errors.sed logfile.txt

Вы также можете добавить #!/usr/bin/sed -f вверху errors.sed и chmod +x, чтобы превратить его в автономный командный сценарий.

person tripleee    schedule 05.10.2011
comment
Спасибо, что поделился. Проблема с этим примером заключается в том, что я бы предпочел сначала получить свой файл журнала - файл errors.txt находится в файле, в то время как журналы иногда поступают из канала. - person yatsek; 05.10.2011
comment
Затем создайте статический скрипт в файле. Я обновлю фрагмент. - person tripleee; 06.10.2011
comment
Удивительно. Работает как шарм. Мне пришлось немного изменить его, чтобы он соответствовал реальному сценарию (не этому общему примеру), и я боялся, что размер файла errors.txt будет проблемой, но это быстрее, чем подход на основе Python или Perl. Мне все еще нужно проверить, как часто обновляются файлы errors.txt, и подумать, будет ли это решение работать на самом деле. Большое спасибо! - person yatsek; 06.10.2011

Я не знаю, сработает ли это, так как я не могу получить ответ о том, сохраняются ли группы захвата, но задать нужно гораздо больше, чем просто команду s. Я подумал, что вы могли бы использовать группу захвата в селекторе строк регулярных выражений, а затем использовать ее для подстановки команд. Что-то вроде этого:

/ERRORID:\(0x[0-9a-f]*\)/  s/ERRORID:0x[0-9a-f]*/ERROR:$(grep \1 errors.txt | grep -o '^[A-Z_]*' )/

В любом случае, если это не сработает, я бы переключил передачу и указал, что это действительно хорошая работа для Perl. Вот как я бы это сделал, что, на мой взгляд, намного чище/проще для понимания:

#!/usr/bin/perl

while(<>) {
  while( /ERRORID:(0x[0-9a-f]*)/ ) {
    $name = system("grep $1 errors.txt | grep -o '^[A-Z_]*'");
    s/ERRORID:$1/ERROR:$name/g;
  }
  print;
}

Затем выполните:

./thatScript.pl logfile.txt
person Chriszuma    schedule 05.10.2011
comment
@tripleee Пожалуйста, уточните (вы имели в виду вызов thatScript.pl? Потому что да, я это исправил. - person Chriszuma; 05.10.2011
comment
Первый пример не сработает. Это потому, что (как я полагаю) подстановка команд происходит сначала - и это также проблема с моим подходом. Переключение передач может быть вариантом, но я надеялся, что какие-то указатели по awk или sth. похожий. Perl может подойти мне, но не обязательно в клиентской среде. В любом случае спасибо за хороший фрагмент perl. - person yatsek; 05.10.2011
comment
@tripleee А, теперь я понимаю, что ты имеешь в виду. Да, grep вполне способен открыть сам файл. - person Chriszuma; 06.10.2011
comment
@tripleee Я не согласен с таким аргументом. Примеры, подобные приведенному выше, часто используются с трубой. И проще поставить какую-нибудь кошку в начале, чтобы потом можно было просто удалить ее и получить работающий фрагмент. OTOH очевидно, что внутри скрипта необходима некоторая оптимизация. - person yatsek; 06.10.2011

С GNU awk для gensub() и аргументом 3rg для match():

$ awk '
    NR==FNR {
        map[$NF] = gensub(/,[^,]+$/,"",1)
        next
    }
    match($0,/(.*ERRORID:)(0x[[:xdigit:]]+)(.*)/,a) {
        $0 = a[1] (a[2] in map ? map[a[2]] : a[2]) a[3]
    }
1' errors.txt logfile.txt
Lot's of meaningless stuff ERRORID:LONG_ERROR_DESCRIPTION and something else =>

Вышеприведенное будет работать намного быстрее, чем сценарии sed в принятом в настоящее время ответе, и не будет давать сбой при различном возможном содержимом LONG_ERROR_DESCRIPTION, таком как % или & или \1, а также не будет сбоя, когда данный ERRORID является подмножеством другого, например. если 0xdead и 0xdeadbeef являются двумя отдельными кодами ошибок, то сценарии sed могут дать сбой в зависимости от порядка их появления в файле errors.txt, например. они могли преобразовать ERRORS:0xdeadbeef в ERRORS:LONG_ERROR_DESCRIPTIONbeef. сначала отобразив 0xdead.

person Ed Morton    schedule 19.07.2018

Просто чтобы люди искали решение с голой оболочкой и sed. Не идеально, но работает:

cat logfile.txt | while read line ; do id=$(echo -E "$line" | 
    grep "ERRORID:0x[0-9a-f]*" | grep -o "0x[0-9a-f]*" ) ; 
    if [ ! -z "$id" ] ; then echo -E "$line" | sed "s/$id/$(grep $id errors.txt | 
    grep -o '^[A-Z_]*' )/g" ;else echo -E "$line" ; fi ; done

Если вы видите какие-то варианты исправления, пожалуйста, поделитесь.

person yatsek    schedule 06.10.2011