Как изобразить много частей в строке соответствия суб-gsub / awk - PullRequest
0 голосов
/ 06 мая 2019

Как представить более одной части подстроки awk sub или gsub.

Для регулярных выражений типа "## code", если я хочу вставить слово между "##" и "code", я бы хотел, чтобы синтаксис VSCode в witch $ 1 представлял первую часть, а $ 2 представлял вторую часть

sub(/(##)(code)/, "$1before$2", str)

из руководства пользователя awk, я обнаружил, что awk использует & для представления всей совпадающей строки.。 Как я могу представить одну, две или более частей в совпадающей строке, как VSCode.

sub (регулярное выражение, замена [, цель]) Цель поиска, которая обрабатывается как строка, для самой левой, самой длинной подстроки, совпадающей с регулярным выражением regexp. Измените всю строку, заменив сопоставленный текст заменой. Измененная строка становится новым значением цели. Вернуть количество выполненных замен (ноль или единицу).

Аргументом регулярного выражения может быть либо константа регулярного выражения (/… /), либо строковая константа («…»). В последнем случае строка обрабатывается как регулярное выражение для сопоставления. См. Раздел «Вычисляемые регулярные выражения» для обсуждения различий между двумя формами и последствий для правильного написания вашей программы.

Эта функция своеобразна, потому что target не просто используется для вычисления значения, а подходит не только для любого выражения - это должна быть переменная, поле или элемент массива, чтобы sub () могла хранить там измененное значение. Если этот аргумент опущен, то по умолчанию используется и изменяется $ 0,48. Например:

str = "вода, вода везде" sub (/ at /, "ith", str) устанавливает str в значение «увядать, вода повсюду», заменяя крайнее левое вхождение «at» на «ith».

Если при замене появляется специальный символ ‘&’, он обозначает точную подстроку, которая соответствует регулярному выражению. (Если регулярное выражение может соответствовать более чем одной строке, эта точная подстрока может отличаться.) Например:

{ sub(/candidate/, "& and his wife"); print }

изменяет первое вхождение слова «кандидат» на «кандидат и его жена» в каждой строке ввода. Вот еще один пример:

Ссылка на руководство пользователя здесь

Ответы [ 2 ]

2 голосов
/ 06 мая 2019

Ваш лучший вариант - использовать GNU awk для любого из них:

$ awk '{$0=gensub(/(##)(code)/,"\\1before\\2",1)} 1' <<<'##code'
##beforecode

$ awk 'match($0,/(##)(code)/,a){$0=a[1] "before" a[2]} 1' <<<'##code'
##beforecode

Первый позволяет перемещать только текстовые сегменты, а второй позволяет вызывать функции, выполнять математические операции или делать что-либо еще.на соответствующий текст, прежде чем перемещать его в оригинале или делать с ним что-то еще:

$ awk 'match($0,/(##)(code)/,a){$0=length(a[1])*10 "before" toupper(a[2])} 1' <<<'##code'
20beforeCODE

Подумав немного, я не знаю, как получить желаемое поведение любым разумным способомиспользуя только POSIX awk конструкции.Вот что я попробовал (функция matches()):

$ cat tst.awk
BEGIN {
    str = "foobar"
    re  = "(f.*o)(b.*r)"
    printf "\nre \"%s\" matching string \"%s\"\n", re, str

    print "succ: gensub():  ", gensub(re,"<\\1> <\\2>",1,str)
    print "succ: match():   ", (match(str,re,a) ? "<" a[1] "> <" a[2] ">" : "")
    print "succ: matches(): ", (matches(str,re,a) ? "<" a[1] "> <" a[2] ">" : "")

    str = "foofoo"
    re  = "(f.*o)(f.*o)"
    printf "\nre \"%s\" matching string \"%s\"\n", re, str

    print "succ: gensub():  ", gensub(re,"<\\1> <\\2>",1,str)
    print "succ: match():   ", (match(str,re,a) ? "<" a[1] "> <" a[2] ">" : "")
    print "fail: matches(): ", (matches(str,re,a) ? "<" a[1] "> <" a[2] ">" : "")
}

function matches(str,re,arr,    start,tgt,n,i,segs) {
    delete arr
    if ( start=match(str,re) ) {
        tgt = substr($0,RSTART,RLENGTH)
        n = split(re,segs,/[)(]+/) - 1
        for (i=1; RSTART && (i < n); i++) {
            if ( match(str,segs[i+1]) ) {
                arr[i] = substr(str,RSTART,RLENGTH)
                str = substr(str,RSTART+RLENGTH)
            }
        }
    }
    return start
}

.

$ awk -f tst.awk

re "(f.*o)(b.*r)" matching string "foobar"
succ: gensub():   <foo> <bar>
succ: match():    <foo> <bar>
succ: matches():  <foo> <bar>

re "(f.*o)(f.*o)" matching string "foofoo"
succ: gensub():   <foo> <foo>
succ: match():    <foo> <foo>
fail: matches():  <foofoo> <>

, но, конечно, это не работает для второго случая в качестве первого сегмента RE f.*o соответствует всей строке foofoo, и, конечно, то же самое происходит, если вы пытаетесь взять RE сегменты в обратном порядке.Я также рассмотрел получение сегментов RE, как описано выше, но затем строил новую строку по одному символу за раз из переданной строки и сравнивал первый сегмент RE с THAT, пока он не совпадет, поскольку TH будет самой короткой подходящей строкой для сегмента RE, НОэто не подходит для строки + RE, например:

str='foooobar'
re='(f.*o)(b.*r)'

, поскольку f.*o будет соответствовать foo с этим алгоритмом, когда оно действительно должно соответствовать fooooo.

Итак - яПолагаю, вам нужно будет продолжать выполнять итерации (внимательно следя за тем, в каком направлении вы выполняете итерацию - с конца, как я полагаю, правильно), пока строка не будет разбита на сегменты, каждый из которых соответствует каждому сегменту RE самым левым-самым длинным образом.Похоже, много работы!

0 голосов
/ 06 мая 2019

Когда вы используете GNU awk, вы можете использовать gensub для этой цели. Без gensub для любого общего awk это становится немного более утомительным. Процедура может быть примерно такой:

ere="(ere1)(ere2)"
match(str,ere)
tmp=substr(str,RSTART,RLENGTH)
match(tmp,"ere1"); part1=substr(tmp,RSTART,RLENGTH)
part2=substr(tmp,RLENGTH)
sub(ere,part1 "before" part2,str)

Проблема в том, что он не всегда будет работать, и вам придется немного его разработать. Простая ошибка может быть создана из-за жадности ERE ":

str="foocode"
ere="(f.*o)(code)"
match(str,ere)                    # finds "foocode"
tmp=substr(str,RSTART,RLENGTH)    # tmp <: "foocode"
match(tmp,"(f.*o)");              # greedy "fooco"
part1=substr(tmp,RSTART,RLENGTH)  # part1 <: "fooco"
part2=substr(tmp,RLENGTH)         # part2 <: "de"
sub(ere,part1 "before" part2,str) # :> "foocobeforede
...