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

Команда ip route ls даст список активных маршрутов и их источников:

caleburn: ~/ >ip route ls
192.168.10.0/24 dev eth0  proto kernel  scope link  src 192.168.10.7 
default via 192.168.10.254 dev eth0 
13
19.06.2013, 19:56
4 ответа

Попробуйте это (простофиля необходима).

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

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

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

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.

12
27.01.2020, 19:52
  • 1
    Спасибо! интересно, как заставить его работать, если эти два числа, например, 1 дюйм и "#1" отличаются? –  StackExchange for All 20.11.2011, 02:03
  • 2
    этот ответ работает на Ваш текущий reqirement/example., если требование изменяется, возможно, Вы могли бы отредактировать вопрос и дать лучший пример. и из Вашего кода awk -F'#', кажется, что Вы только хотите сделать изменение на части после '#'? –  Kent 20.11.2011, 02:10
  • 3
    Спасибо за Ваше предложение. Я просто изменил свой пример так, чтобы эти два числа не были тем же. –  StackExchange for All 20.11.2011, 02:12
  • 4
    @Tim видит мой обновленный ответ для Вашего нового примера. –  Kent 20.11.2011, 02:28
  • 5
    Спасибо! Некоторые вопросы: (1) делает FS=OFS="\" \"#" подразумевайте, что разделитель поля и во вводе и выводе удваивает кавычку, пространство, двойная кавычка и в #? почему указывают двойную кавычку дважды? (2) в /.* ([0-9]+)$/, делает $ иметь в виду конец строки? (3) в третьем аргументе gensub (), между чем различие "g" и "G"? –  StackExchange for All 20.11.2011, 22:56

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

Отметьте также это даже если \1 поддерживался, Ваш отрывок добавит строку +11, не выполняют числовое вычисление. Кроме того, Ваш regexp не совершенно правилен, Вы соответствуете вещам как "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'
7
27.01.2020, 19:52
  • 1
    Первое предложение Вашего ответа точно, что я искал. Однако то, что Вы сказали "... в тексте замены", поднимает последующий вопрос: awk позволяет обратные ссылки в самом regex шаблоне? –  Wildcard 04.11.2015, 19:57
  • 2
    @Wildcard нет, awk просто не отслеживает группы (за исключением расширения GNU, которое я упоминаю). –  Gilles 'SO- stop being evil' 04.11.2015, 20:16

awk может сделать это, но это не является прямым, даже с помощью backreferencing.
GNU awk имеет (частичный) backreferecing, в форме gensub.

Экземпляры 123" временно перенесены в \x01 и \x02 отметить их, как не изменено (для sub(). co

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

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 и массив 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" }'
5
27.01.2020, 19:52
  • 1
    Спасибо! В Вашем первом коде, (1), что делает "\x01\\1\"\x02" средний? Я все еще не понимаю \x01 и \x02. (2), насколько отличающийся возврат $0 gensub и $0 как последний аргумент gensub? –  StackExchange for All 20.11.2011, 23:04
  • 2
    @Tim. Шестнадцатеричные значения \x01 и \x02 используются в качестве маркеров замены. Эти значения очень вряд ли будут в любом файле обычного текста, таким образом, они будут одинаково "очень" безопасны использовать (т.е. не встретиться со столкновением с существующими ранее).. Они - просто временные маркировки.. Ре $0=gensub(... $0).. посмотрите эту Обработку строк ссылки Функции, но таким образом: Это (gensub) возвращает измененную строку как результат функции, и исходная целевая струна не поменялась.... $0= просто изменяет исходную цель.. –  Peter.O 23.11.2011, 13:37

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

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

Объяснение:

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

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

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

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

perl -wpe 's/(\d+)"/$1+11 . q{"}/eg' < in.txt > out.txt
2
27.01.2020, 19:52

Теги

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