Как использовать регулярное выражение с AWK для замены строки?

Предположим, что есть некоторый текст из файла:

(bookmarks ("Chapter 1 Introduction 1" "#1" ("1.1 Problem Statement and Basic Definitions 23" "#2") ("Exercises 31" "#30") ("Notes and References 42" "#34")) ) 

Я хочу добавить 11 к каждому номеру, а затем " в каждой строке, если есть, т. Е.

 (bookmarks ("Chapter 1 Introduction 12" "#12" ("1.1 Problem Statement and Basic Definitions 34" "#13") ("Exercises 42" "#41") ("Notes and References 53" "#45")) ) 

Вот мое решение, используя GNU AWK и regex:

 awk -F'#' 'NF>1{gsub(/"(\d+)\""/, "\1+11\"")}' 

т.е. я хочу заменить (\d+)\" на \1+10\" , где \1 – группа, представляющая (\d+) . Но это не сработает. Как я могу заставить его работать?

Если gawk – не лучшее решение, что еще можно использовать?

  • Катить все файлы в папке, включая имя файла, используя цикл for?
  • Поиск файла для строки, окруженной двумя другими известными строками?
  • Разбор строк файла данных с помощью разделителя Вместо EOL
  • Как перезаписать stdout с помощью эха?
  • Как удалить несколько строк в каждом вхождении в файл?
  • Искать текст в Linux, но не отображать полную строку в результатах
  • Регулярные команды и команды с командой sed
  • попытка добавить текст до и после некоторого текста
  • 4 Solutions collect form web for “Как использовать регулярное выражение с AWK для замены строки?”

    Попробуйте это (требуется gawk).

     awk '{a=gensub(/.*#([0-9]+)(\").*/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+\"/,a+11"\"",$0);}print $0}' YourFile 

    Тест с вашим примером:

     kent$ echo '(bookmarks ("Chapter 1 Introduction 1" "#1" ("1.1 Problem Statement and Basic Definitions 2" "#2") ("Exercises 30" "#30") ("Notes and References 34" "#34")) ) '|awk '{a=gensub(/.*#([0-9]+)(\").*/,"\\1","g",$0);if(a~/[0-9]+/) {gsub(/[0-9]+\"/,a+11"\"",$0);}print $0}' (bookmarks ("Chapter 1 Introduction 12" "#12" ("1.1 Problem Statement and Basic Definitions 13" "#13") ("Exercises 41" "#41") ("Notes and References 45" "#45")) ) 

    Обратите внимание, что эта команда не будет работать, если два номера (например, 1 "и" # 1 ") отличаются друг от друга, или есть больше номеров в одной строке с этим шаблоном (например, 23" … 32 "…" # 123 ") в одной строке.

    ОБНОВИТЬ

    Поскольку @Tim (OP) сказал, что число, за которым следует " в одной строке, может отличаться, я сделал некоторые изменения в своем предыдущем решении и запустил его для вашего нового примера.

    Кстати, из примера я чувствую, что это может быть таблица структуры контента, поэтому я не вижу, как два числа могут быть разными. Сначала будет напечатан номер страницы, а второй с # будет индексом страницы. Я прав?

    В любом случае, вы лучше всего знаете свое требование. Теперь новое решение, все еще с gawk (я разбиваю команду на строки, чтобы было легче читать):

     awk 'BEGIN{FS=OFS="\" \"#"}{if(NF<2){print;next;} a=gensub(/.* ([0-9]+)$/,"\\1","g",$1); b=gensub(/([0-9]+)\"/,"\\1","g",$2); gsub(/[0-9]+$/,a+11,$1); gsub(/^[0-9]+/,b+11,$2); print $1,$2 }' yourFile 

    тест с новым примером:

     kent$ echo '(bookmarks ("Chapter 1 Introduction 1" "#1" ("1.1 Problem Statement and Basic Definitions 23" "#2") ("Exercises 31" "#30") ("Notes and References 42" "#34")) ) '|awk 'BEGIN{FS=OFS="\" \"#"}{if(NF<2){print;next;} a=gensub(/.* ([0-9]+)$/,"\\1","g",$1); b=gensub(/([0-9]+)\"/,"\\1","g",$2); gsub(/[0-9]+$/,a+11,$1); gsub(/^[0-9]+/,b+11,$2); print $1,$2 }' (bookmarks ("Chapter 1 Introduction 12" "#12" ("1.1 Problem Statement and Basic Definitions 34" "#13") ("Exercises 42" "#41") ("Notes and References 53" "#45")) ) 

    EDIT2 на основе комментария @Tim

    (1) Значение FS = OFS = "\" \ "#" означает, что разделитель поля как на входе, так и на выходе – это двойная кавычка, пробел, двойная кавычка и #? Зачем дважды указывать двойную кавычку?

    Вы правы для разделителя как в входной, так и выходной части. Он определил разделитель как:

     " "# 

    Есть две двойные кавычки, потому что легче поймать два числа, которые вы хотите (на основе вашего примера ввода).

    (2) В /.* ([0-9] +) $ /, означает ли $ конец строки?

    В точку!

    (3) В третьем аргументе gensub (), в чем разница между «g» и «G»? нет никакой разницы между G и g. Проверь это:

     gensub(regexp, replacement, how [, target]) # Search the target string target for matches of the regular expression regexp. If "how" is a string beginning with 'g' or 'G' (short for “global”), then replace all matches of regexp with replacement. 

    Это от http://www.gnu.org/s/gawk/manual/html_node/String-Functions.html . вы можете прочитать, чтобы получить подробное использование gensub.

    В отличие от всех инструментов, которые предоставляют подстановки regexp, awk не разрешает обратные ссылки, такие как \1 в тексте замещения. GNU Awk предоставляет доступ к сопоставленным группам, если вы используете функцию соответствия , но не с ~ или sub или gsub .

    Также обратите внимание, что даже если поддерживается \1 , ваш фрагмент будет добавлять строку +11 , а не выполнять численное вычисление. Кроме того, ваше регулярное выражение не совсем правильно, вы согласуете такие вещи, как "42"" а не "#42" .

    Вот решение awk (предупреждение, непроверенное). Он выполняет только одну замену на строку.

     awk ' match($0, /"#[0-9]+"/) { n = substr($0, RSTART+2, RLENGTH-3) + 11; $0 = substr($0, 1, RSTART+1) n substr($0, RSTART+RLENGTH-1) } 1 {print}' 

    Это было бы проще в Perl.

     perl -pe 's/(?<="#)[0-9]+(?=")/$1+11/e' 

    awk может это сделать, но он не является прямым, даже используя обратную связь.
    GNU awk имеет (частичную) обратную настройку в виде gensub .

    Экземпляры 123" временно завернуты в \x01 и \x02 чтобы пометить их как немодифицированные (для sub() . Co

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

     awk '{$0=gensub(/([0-9]+)\"/, "\x01\\1\"\x02", "g", $0 ) while ( match($0, /\x01[0-9]+\"\x02/) ) { temp=substr( $0, RSTART, RLENGTH ) numb=substr( temp, 2, RLENGTH-3 ) + 11 sub( /\x01[0-9]+\"\x02/, numb "\"" ) } print }' 

    Вот еще один способ: использовать gensub и array split и \x01 в качестве разделителя полей (для разделения ). \ X02 отмечает элемент массива как кандидата для арифметического добавления.

     awk 'BEGIN{ ORS="" } { $0=gensub(/([0-9]+)\"/, "\x01\x02\\1\x01\"", "g", $0 ) split( $0, a, "\x01" ) for (i=0; i<length(a); i++) { if( substr(a[i],1,1)=="\x02" ) { a[i]=substr(a[i],2) + 11 } print a[i] } print "\n" }' 

    Поскольку решения в (g) awk кажутся довольно сложными, я хотел бы добавить альтернативное решение в Perl:

     perl -wpe 's/\d+(?=")/$&+11/eg' < in.txt > out.txt 

    Объяснение:

    • Опция -w включает предупреждения (которые предупреждают вас о возможных нежелательных эффектах).
    • Опция -p подразумевает цикл вокруг кода, который работает аналогично sed или awk, сохраняя каждую строку ввода автоматически в переменной по умолчанию, $_ .
    • Параметр -e указывает perl, что программный код указан в командной строке, а не в файле сценария.
    • Код представляет собой замену регулярного выражения ( s/.../.../ ) на $_ , где последовательность цифр, если за ним следует s/.../.../ " , будет заменена последовательностью, интерпретируемой как число в дополнении , плюс 11.
    • Утверждение с положительной обратной связью с нулевой шириной (?=pattern) ищет " не принимая его в соответствие», поэтому нам не нужно повторять его при замене. Переменная MATCH $& в замене будет содержать только номер.
    • Модификатор /e для регулярного выражения сообщает perl «выполнить» замену как код вместо того, чтобы принимать его как строку.
    • Модификатор /g делает замену «глобальной», повторяя ее при каждом совпадении в строке.

    Переменная MATCH $& , к сожалению, будет вредной для производительности кода в версиях Perl до 5.20. Более быстрое (и не намного более сложное) решение будет использовать группировку и backreference $1 вместо:

     perl -wpe 's/(\d+)?="/$1+11/eg' < in.txt > out.txt 

    И если прогнозное утверждение выглядит слишком запутанным, вы также можете заменить кавычек явно:

     perl -wpe 's/(\d+)"/$1+11 . q{"}/eg' < in.txt > out.txt 
    Linux и Unix - лучшая ОС в мире.