BSD sed (Mac) Как заменить n-ное вхождение до конца строки? - PullRequest
3 голосов
/ 06 мая 2019

В GNU sed это будет примерно так

's/foo/bar/3g' <<< "foofoofoofoofoo"

Output: "foofoobarbarbar"

Эта же команда в BSD sed выдает мне следующую ошибку

sed: 1: "s/foo/bar/3g": more than one number or 'g' in substitute flags

Как я могу реализовать это на BSD sed?

Я искал SO и нашел это , но все ответы для GNU. Я читаю этого человека, но мне трудно это понять.

Ответы [ 6 ]

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

Если это что-то отличное от простого s / old / new, просто используйте awk вместо sed.С любым awk в любой оболочке на любом компьютере UNIX:

$ cat tst.awk
{
    head = ""
    tail = $0
    cnt  = 0
    while ( match(tail,old) ) {
        tgt = substr(tail,RSTART,RLENGTH)
        if ( ++cnt >= beg ) {
            tgt = new
        }
        head = head tgt
        tail = substr(tail,RSTART+RLENGTH)
    }
    print head tail
}

$ awk -v old='foo' -v new='bar' -v beg=3 -f tst.awk <<< "foofoofoofoofoo"
foofoobarbarbar

Конечно, это несколько строк кода, но это чрезвычайно распространенный код для решения многих проблем, поэтому хорошо знать это, легко увидеть, что он делает, и это легко изменить, чтобы сделать что-нибудь еще.

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

$ cat tst.awk
{
    head = ""
    cnt  = 0
    while ( match($0,old) ) {
        head = head (++cnt < beg ? substr($0,RSTART,RLENGTH) : new)
        $0 = substr($0,RSTART+RLENGTH)
    }
    print head $0
}

или даже до страшного «однострочного»:

awk -v o='foo' -v n='bar' -v b=3 '{h="";c=0;while(s=match($0,o)){h=h (++c<b?substr($0,s,RLENGTH):n);$0=substr($0,s+RLENGTH)}$0=h$0}1' <<< "foofoofoofoofoo"
foofoobarbarbar
3 голосов
/ 06 мая 2019

Один из вариантов заключается в реализации цикла с использованием метки и команды t:

$ sed -e ':l' -e 's/foo/bar/3' -e 'tl' <<< 'foofoofoofoofoo'
foofoobarbarbar

Только будьте осторожны, потому что, если ваш текст замены соответствует вашему исходному RE (например, s/f.x/fox/), вы 'Вы застрянете в бесконечном цикле, и если он генерирует исходный текст после замены, вы получите неожиданные результаты, например:

$ sed 's/foo/oo/3g' <<< 'foofoofffoo'
foofooffoo
$ sed -e ':l' -e 's/foo/oo/3' -e 'tl' <<< 'foofoofffoo'
foofoooo

Обратите внимание, что первая версия работает, потому что она выполняет все заменыза 1 проход текста, поэтому предыдущая замена не считается частью строки для текущего прохода замены.

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

Это может работать для вас:

sed -e ':a' -e 's/foo/\'$'\n/2' -e 'ta' -e 's/\'$'\n/bar/g' file

Установите цикл для n-го вхождения (в этом примере 2) и замените его уникальным символом / строкой (в этом примере новой строкой). Когда цикл завершается неудачно, глобально замените уникальный символ / строку на предполагаемую строку.

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

Если perl в порядке:

$ echo 'foofoofoofoofoo' | perl -pe '$c=0; s/foo/++$c<3 ? $& : "bar"/ge'
foofoobarbarbar
  • $c=0 для каждой строки ввода, инициализировать счетчик
  • e используется для разрешения кода Perl вместо строки в секции замены
  • ++$c<3 ? $& : "bar" на основе счетчика, сохранить или заменить соответствующий текст
2 голосов
/ 06 мая 2019

Еще один в awk * для обработки одной строки:

$ echo foofoofoofoofoo | 
  awk -v n=3 'BEGIN{RS="foo"}{ORS=NR<n?RS:"bar"}1'
foofoobarbarbar

* Успешно протестировано на gawk, mawk и Busybox awk. Ошибка на awk-20121220.

1 голос
/ 06 мая 2019

Вы не можете сделать это без особых затруднений.

Как указано в руководстве GNU sed :

г

Примените замену ко всем совпадениям к регулярному выражению , а не только к первому.

номер

Заменить только число -ное совпадение регулярное выражение .

взаимодействие в команде s Примечание: стандарт POSIX не определяет, что должно происходить при смешивании модификаторов g и число , и в настоящее время нет общепринятого значения в реализациях sed. Для GNU sed взаимодействие определяется следующим образом: игнорировать совпадения до числа -го, а затем сопоставлять и заменять все совпадения с числа -го и далее.)

В Mac OS X, однако, это работает:

▶ sed 's/foo/bar/3' <<< 'foofoofoofoofoo'          
foofoobarfoofoo

Как это сделать:

▶ sed 's/foo/bar/g' <<< 'foofoofoofoofoo'  
barbarbarbarbar

Но ошибка, указанная в вопросе, выдается, если они используются вместе.

@ oguzismail предоставил умное и простое решение, и я добавил это дополнительное объяснение, потому что я думал, что это будет полезно. 1 Более ранняя версия его ответа показала это, который, что смущает, ничего не сделал при тестировании:

▶ sed ':a; s/foo/bar/3; ta' <<< 'foofoofoofoofoo'                                                                                                                      
foofoofoofoofoo

Руководство BSD также не дает никаких объяснений. Тем не менее, руководство POSIX гласит:

Команды b, t и: документированы так, чтобы игнорировать начальные пробелы, но не упоминается о пробелах в конце.

Таким образом, это работает:

▶ sed -e :a -e s/foo/bar/3 -e ta <<< 'foofoofoofoofoo'
foofoobarbarbar

Это также работает:

▶ sed '
    :a
    s/foo/bar/3
    ta
  ' <<< 'foofoofoofoofoo'
foofoobarbarbar

В любом случае сценарий заменяет 3-й foo на bar в цикле до тех пор, пока замена не завершится неудачей, после чего сценарий заканчивается. Обратите внимание на использование t (test), который разветвляется, только если предыдущая команда s/// что-то заменила.

Чтобы понять, что скрипт делает в каждой из итераций своего цикла, полезно:

▶ sed -n -e :a -e s/foo/bar/3p -e ta <<< 'foofoofoofoofoo'
foofoobarfoofoo
foofoobarbarfoo
foofoobarbarbar

1 Первоначальная версия этого ответа не имела объяснения, хотя сейчас она значительно расширена. Огуз указал, что он предпочитает, чтобы я добавил эту информацию в отдельном ответе.

...