Як використовувати регулярний вимір з AWK для заміни рядка?


13

Припустимо, є якийсь текст із файлу:

(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 - не найкраще рішення, що ще можна використати?


Вибачте за дублювання. Але я спершу запитав про stackoverflow, і я не отримав задовільної відповіді, тому я позначив міграцію. Але це не відбувалося деякий час, тому я не очікував, що це станеться, а потім попросив Unix.SE.
Тім

Відповіді:


12

Спробуйте це (потрібен гаук).

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.


Спасибі! Цікаво, як змусити його працювати, якщо два числа, наприклад 1 "та" # 1 ", різні?
Тім

ця відповідь працює для вашого поточного запиту / прикладу. якщо вимога буде змінена, можливо, ви могли б відредагувати питання та надати кращий приклад. а з вашого коду awk -F'#'здається, що ви хочете змінити лише після знака "#"?
Кент

Дякуємо за вашу пропозицію. Я просто змінив свій приклад, щоб два числа не були однаковими.
Тім

@Tim дивіться мою оновлену відповідь для вашого нового прикладу.
Кент

Спасибі! Деякі запитання: (1) FS=OFS="\" \"#"означає, що роздільник поля як вхідного, так і вихідного - це подвійна цитата, пробіл, подвійна цитата та #? чому вказувати подвійну цитату двічі? (2) в /.* ([0-9]+)$/, чи $означає кінець рядка? (3) у третьому аргументі gensub (), яка різниця між "g"та "G"?
Тім

7

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

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

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

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 зворотні посилання в самому шаблоні регулярних виразів?
Wildcard

1
@Wildcard Ні, awk просто не відстежує групи (за винятком розширення GNU, яке я згадую).
Жил "ТАК - перестань бути злим"

5

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і масив, 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" }'

Спасибі! У вашому першому коді (1) що "\x01\\1\"\x02"означає? Я досі не розумію \x01і \x02. (2) як відрізняється віддача $0від gensubі $0як останній аргумент gensub?
Тім

@Tim. У шестнадцатиричное значення \x01і \x02використовуються в якості маркерів заміщення. Ці значення є досить малоймовірно , щоб бути в будь-якому звичайному текстовому файлі, тому вони однаково «високо» безпечно для використання (тобто. Чи не зіткнутися зіткнення з уже існуючими) .. Вони просто тимчасові мітки .. Re $0=gensub(... $0).. бачити це посилання String-Manipulation Functions , але підсумовуючи: Це (gensub) повертає модифіковану рядок як результат функції, а початковий цільовий рядок не змінюється. ... $0=Просто модифікує початкову ціль ..
Peter.O

3

Оскільки рішення в (g) awk здаються досить складними, я хотів додати альтернативне рішення в Perl:

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

Пояснення:

  • Опція -wвмикає попередження (які попереджають вас про можливі небажані ефекти).
  • Опція -pпередбачає петлю навколо коду , який працює аналогічно СЕД або AWK, економлячи кожну рядок введення автоматично в змінному за замовчуванням $_.
  • Опція -eповідомляє perl, що програмний код слід у командному рядку, а не у файлі сценарію.
  • Код є заміною регулярних виразів ( s/.../.../) на $_, де послідовність цифр, якщо за нею слідує а ", буде замінена послідовністю, інтерпретується як додавання число плюс 11.
  • У нульовий ширини позитивний висновок упереджувальний (?=pattern) шукає , "не беручи його в матчі, так що ми не повинні повторювати його заміни. Змінна 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.