2013-02-19 2 views
3

Мне нужно найти текстовый файл для строки и сделать замену, которая включает число, которое увеличивается с каждым совпадением.gsub issue with awk (gawk)

Строка, которая должна быть «найдена», может быть одним символом, словом или фразой.

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

Например:

1) У меня есть тестовый файл с именем "data.txt". Файл содержит:

Now is the time 
for all good men 
to come to the 
aid of their party. 

2) Я поместил awk-скрипт в файл с именем «cmd.awk». Файл содержит:

/f/ {sub ("f","f(" ++j ")")}1 

3) Я использую AWK так:

awk -f cmd.awk data.txt 

В этом случае, выход, как ожидается:

Now is the time 
f(1)or all good men 
to come to the 
aid of(2) their party. 

Проблема возникает, когда есть больше чем одно совпадение на линии. Например, если бы я искал буквы «я», как:

/i/ {sub ("i","i(" ++j ")")}1 

Выход:

Now i(1)s the time 
for all good men 
to come to the 
ai(2)d of their party. 

который является неправильным, поскольку он не включает в себя «я» в «время» или "их".

Итак, я попытался «GSUB» вместо «суб», как:

/i/ {gsub ("i","i(" ++j ")")}1 

Выход:

Now i(1)s the ti(1)me 
for all good men 
to come to the 
ai(2)d of thei(2)r party. 

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

Желательный результат должен быть:

Now i(1)s the ti(2)me 
for all good men 
to come to the 
ai(3)d of thei(4)r party. 

Примечание: Номер не всегда начинается с "1", так что я мог бы использовать AWK так:

awk -f cmd.awk -v j=26 data.txt 

Чтобы получить результат:

Now i(27)s the ti(28)me 
for all good men 
to come to the 
ai(29)d of thei(30)r party. 

И только чтобы быть ясным, число в замене не всегда будет внутри скобок. И замена не всегда будет включать согласованную строку (на самом деле это было бы довольно редко).

Другая проблема, которую я имею с этим ...

Я хочу использовать (не переменное окружение) AWK-переменной «поиск строки», так что я могу указать это в командной строке AWK ,

Например:

1) Я поместил сценарий AWK в файле с именем "cmd.awk". Файл содержит что-то вроде:

/??a??/ {gsub (a,a "(" ++j ")")}1 

2) я хотел бы использовать AWK так:

awk -f cmd.awk -v a=i data.txt 

Чтобы получить выход:

Now i(1)s the ti(2)me 
for all good men 
to come to the 
ai(3)d of thei(4)r party. 

вопрос здесь, как я представляю переменная «a» в/search/expression?

ответ

2

AWK:

awk '{for(i=2; i<=NF; i++)$i="(" ++k ")" $i}1' FS=i OFS=i 
+0

Мне пришлось поиграть с кавычками, чтобы он работал в командной строке Windows, вот что я придумал: 'awk" BEGIN {FS = \ "i \"; OFS = \ "i \"; k = 0} {для (i = 2; i <= NF; i ++) $ i = \ "(\" ++ k \ ") \" $ i} 1 "" data.txt "'. Можете ли вы немного объяснить, почему в некоторых вариантах использования переменной «i» предшествует «$» («$ i»), а некоторые нет? –

+0

Переменные не используют $ -sign, но поля ($ 1, $ 2, ..) делают и сама запись ($ 0). Но если i = 5, например, тогда $ i означает $ 5. – Scrutinizer

+0

@KevinFegan не используют awk-скрипты в командной строке в Windows, иначе у вас появятся кавычки и другие проблемы. Вместо этого поместите скрипт в файл с именем, скажем, «foo.awk» и запустите его как awk -f foo.awk data.txt. Возможно, вы захотите изучить установку cygwin, чтобы избежать кошмара, который является Windows для сценариев. –

1

Я не говорю, что это невозможно сделать, используя awk, но я настоятельно рекомендую перейти на более мощный язык. Вместо этого используйте perl.

Чтобы включить счетчик письма i, начиная с 26, попробуйте:

perl -spe 's:i:$&."(".++$x.")":ge' -- -x=26 data.txt 

Это также может быть оболочка вар:

var=26 
perl -spe 's:i:$&."(".++$x.")":ge' -- -x=$var data.txt 

Результаты:

Now i(27)s the ti(28)me 
for all good men 
to come to the 
ai(29)d of thei(30)r party. 

Включить счет s отличные слова, добавить границы слов (т. \b) вокруг слов, попробуйте:

perl -spe 's:\bthe\b:$&."(".++$x.")":ge' -- -x=5 data.txt 

Результаты: версия

Now is the(6) time 
for all good men 
to come to the(7) 
aid of their party. 
2

gensub() звучит идеально подходит здесь, это позволяет заменить Nth матч, так, что звучит как решение - перебирать строку в цикле do{}while(), заменяя одно совпадение за раз и увеличивая j. Этот простой подход gensub() не будет работать, если замена не содержит исходного текста (или, что еще хуже, содержит его несколько раз), см. Ниже.

Так в AWK, не хватает «s///e» особенности языка Perl оценки, и его модификатора с сохранением состояния регулярного выражения /g (используется Стив) лучший оставшийся вариант должны разбить строки на куски (голов, матча, хвоста) и наклеить их снова вместе:

BEGIN { 
    if (j=="") j=1 
    if (a=="") a="f" 
} 
match($0,a) { 
    str=$0; newstr="" 
    do { 
     newstr=newstr substr(str,1,RSTART-1) # head 
     mm=substr(str,RSTART,RLENGTH)  # extract match 
     sub(a,a"("j++")",mm)     # replace 
     newstr=newstr mm 
     str=substr(str,RSTART+RLENGTH)  # tail 
    } while (match(str,a)) 
    $0=newstr str  
} 
{print} 

Это использует match() как epxression вместо // узора, так что вы можете использовать переменную. (Вы также можете просто использовать «($0 ~ a) { ... }», но результаты match() используются в этом коде, так что не пытайтесь что здесь.)

Вы можете определить j и a в командной строке.

gawk поддерживает \y, который является эквивалентом perlre-х \b, а также поддерживает \< и \> к Явно соответствует начало и конец слова, просто позаботьтесь, чтобы добавить дополнительные побеги из командной строки Unix (я не совсем что Windows может потребовать или разрешить).


Limited gensub() версия

Как указано выше:

match($0,a) { 
    idx=1; str=$0 
    do { 
     prev=str 
     str=gensub(a,a"(" j ")",idx++,prev) 
    } while (str!=prev && j++) 
    $0=str 
} 

проблемы здесь:

  • если вы заменить подстроку "i" с подстроки "k" или "k(1)", затем e gensub() индекс для следующего матча будет отключен на 1. Вы можете обойти это, если вы либо заранее знаете это, либо работаете обратно через строку.
  • если заменить подстроку «i» с подстроки «ii» или «ii(i)», то возникает аналогичная проблема (в результате чего в бесконечном цикле, потому что gensub() продолжает находить новый матч)

Dealing с обоими условиями робастно не стоит кода.

+0

Вы сказали: «Это не сработает, если замена НЕ содержит оригинальный текст». Можете ли вы объяснить, почему, и предоставить пример «gensub()»? Кроме того, в случае, когда я хотел заменить только первое совпадение строки, можете ли вы описать, как я могу представить переменную «a» в/search/expression, например: '/ ?? a ??/{sub (a, a "(" ++ j ")")} 1'? –

+0

'gensub()' добавлено. Используйте 'match() {...}', как указано выше, или поскольку '' // '" действительно эквивалентно '($ 0 ~ //)' вы можете использовать '($ 0 ~ a) {...}' вместо , где 'a' - это переменная регулярного выражения (не используйте начальную/конечную'/', хотя в переменной). Если вам не требуется захват или что-то еще, подход @ Scrutinizer * намного проще. –

+0

', если вы замените« i »на« k (1) »« - Я не вижу, где вы определяете переменную «k» ... или «k» просто строковый литерал? –