Обновление 26.05.2020
Похоже, это была ошибка, поэтому я подал сообщение об ошибке. Ее идентификатор — #41558.
Я просто баловался sed
и придумал такое упражнение: заменить третье с конца вхождение «and» (слово, а не подстроку), чтобы получилось:
dog XYZ foo and bar and baz land good
Я думал, это сработает.
echo 'dog and foo and bar and baz land good' |
sed -E 's/(.*)\band\b((.*\band\b){2})/\1XYZ\2/'
но на самом деле он заменяет предпоследнее появление "and". Единственное объяснение, которое я могу придумать, это то, что он включает "land" как один из \band\b
, но это не должно быть так, потому что я включил \b
границы слова?
решение1
Это трудно сделать, так как sed
не поддерживает обходы и т. д. (как это можно сделать в PCRE). Было бы проще перевернуть строку и заменить третье вхождение перевернутого слова с самого начала, а затем перевернуть еще раз.
$ echo 'dog and foo and bar and baz land good' | rev | sed 's/\<dna\>/XXX/3' | rev
dog XXX foo and bar and baz land good
Что касается того, почему ваше выражение не работает, это похоже на ошибку. Обратная ссылка, \3
похоже, является строкой baz land
, как будто \b
before and
in .*\band\b
никогда не имел никакого эффекта.
Команда
sed -E 's/(.*)\<and\>((.*\<and\>){2})/\1XYZ\2/'
похоже, что в OpenBSD все работает правильно с его собственным sed
(который использует \<
и \>
вместо \b
).
Мне еще предстоит найти существующий отчет об ошибке в GNU sed
или GNU glibc
по этому поводу, хотя я не удивлюсь, если он будет, по крайней мересвязанныйкошибка glibc 25322(потому что, см. ниже).
Вы можете обойти это, если будете немного более многословны:
sed -E 's/(.*)\band\b(.*\band\b.*\band\b)/\1XYZ\2/'
решение2
Я бы предложил подать заявку на решение проблемы. Я протестировал эти примеры, и они дают одинаковое поведение с GNU grep
, GNU sed
и GNU awk
. За исключением одного случая, который отмечен ниже.
Неправильный вывод:
$ echo 'cocoa' | sed -nE '/(\bco){2}/p' cocoa
sed -nE '/(\<co){2}/p'
иawk '/(\<co){2}/'
тоже имеет глючное поведение, ноgrep -E '(\<co){2}'
правильно не выдает выходных данныхПравильное поведение, нет вывода:
$ echo 'cocoa' | sed -nE '/\bco\bco/p'
it
Неправильный вывод: после есть только одно целое словоwith
$ echo 'it line with it here sit too' | sed -E 's/with(.*\bit\b){2}/XYZ/' it line XYZ too
Правильное поведение, входные данные не изменяются
$ echo 'it line with it here sit too' | sed -E 's/with.*\bit\b.*\bit\b/XYZ/' it line with it here sit too
Изменение границ слов на
\<
и\>
приводит к другой проблеме.Это правильноне изменяетвходные данные:
$ echo 'it line with it here sit too' | sed -E 's/with(.*\<it\>){2}/XYZ/' it line with it here sit too
Это правильно изменяет входные данные
$ echo 'it line with it here it too' | sed -E 's/with(.*\<it\>){2}/XYZ/' it line XYZ too
Но этот не может изменить входные данные.
$ echo 'it line with it here it too sit' | sed -E 's/with(.*\<it\>){2}/XYZ/' it line with it here it too sit
Кроме того, проблемное поведение наблюдается только в том случае, если конфликтующее слово имеет дополнительные символы в начале. Например, it
и sit
. Но не в случае, если символы есть в конце. Например, it
и site
и item
.
$ echo 'it line with it here item too' | sed -E 's/with(.*\bit\b){2}/XYZ/'
it line with it here item too
$ echo 'it line with it here it too item' | sed -E 's/with(.*\<it\>){2}/XYZ/'
it line XYZ too item