Intereting Posts
Почему я не могу использовать команду time в подробном режиме без указания всего пути команды? Unix, Mac OS и Linux? Сброс многоадресного потока UDP с помощью socat Использование акцентов в Vim плавно Какие правила определяют отношения между родителями и дочерними процессами, запущенными оболочкой через трубу? Создание Fluxbox выглядит довольно Как перейти на другого пользователя после входа в SFTP Как освободить место на полностью полном объеме? Как получить доступ к http-серверу из сценария bash с существующим подключением tcp? Термопринтер печатает только после отправки второго вывода данных на порт tty Конвертировать порядок поиска … -exec дочерний процесс не наследует ожидающие сигналы от родителя после системного вызова fork, почему? Кто-нибудь знает приложение динамического лирического дисплея для mpd, которое работает в Arch Linux? Получить рекурсивный счетчик файлов (например, `du`, но количество файлов вместо размера) Различные размеры шрифта консоли на двух установках debian wheezy

Как использовать регулярное выражение с 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 – не лучшее решение, что еще можно использовать?

Попробуйте это (требуется 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