Sed не заменяет все экземпляры в файле, когда области перекрываются - PullRequest
4 голосов
/ 06 января 2012

Мне нужно заменить несколько слов другими словами.

Например, «яблоко» с «ФРУКТАМИ» в file, только в следующих 4 ситуациях:

  • _apple_, имеет пробел до и после.
  • [apple_, имеет квадратную открывающую скобку до и пробел после.
  • _apple], имеет пробел до и квадратную скобку после.
  • [apple], имеет квадратные скобки до и после.

Я не хочу, чтобы замены происходили в любой другой ситуации.

Я пытался использовать следующий код:

a="apple"
b="fruit"
sed -i "s/ $a / $b /g" ./file
sed -i "s/\[$a /\[$b /g" ./file
sed -i "s/ $a\]/ $b\]/g" ./file
sed -i "s/\[$a\]/\[$b\]/g" ./file

Я думал, что опция "g" в конце будет означать, что она заменит все экземпляры, но я обнаружил, что это не полное решение Например, если file содержит это:

apple spider apple apple spider tree apple tree

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

apple  spider apple apple apple apple apple spider tree apple tree

Я подозреваю, что это потому, что общее "пространство".

Как я могу получить это, чтобы найти и заменить все экземпляры $a на $b, независимо от совпадения?

Ответы [ 5 ]

3 голосов
/ 08 января 2012

Быстрое и грязное решение состоит в том, чтобы выполнить замену дважды.

$ echo apple apple apple apple[apple apple] | sed -e 's/\(\[\| \)apple\( \|\]\)/\1FRUIT\2/g; s/\(\[\| \)apple\( \|\]\)/\1FRUIT\2/g'
apple FRUIT FRUIT apple[FRUIT FRUIT]

Это безопасно, поскольку после первой команды полученный текст не будет содержать никаких вхождений (\[| )apple( |\]), которыееще не было в исходном тексте.

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

Если разбить его на два выполнения sed ,Вы можете видеть шаги яснее:

$ echo apple apple apple apple apple apple[apple apple] | sed -e 's/\(\[\| \)apple\( \|\]\)/\1FRUIT\2/g'
apple FRUIT apple FRUIT apple apple[FRUIT apple]

$ echo apple FRUIT apple FRUIT apple apple[FRUIT apple] | sed -e 's/\(\[\| \)apple\( \|\]\)/\1FRUIT\2/g'
apple FRUIT FRUIT FRUIT FRUIT apple[FRUIT FRUIT]
3 голосов
/ 06 января 2012

Вы можете сделать это с помощью обратных ссылок.Это должно быть полностью POSIX-совместимым

sed -i 's/^badger\([] ]\)/SNAKE\1/g; \
        s/\([[ ]\)badger$/\1SNAKE/g; \
        s/\([[ ]\)badger\([] ]\)/\1SNAKE\2/g; \
        s/ badger]/ SNAKE]/g' ./infile

Пример

$ sed 's/^badger\([] ]\)/SNAKE\1/g;s/\([[ ]\)badger$/\1SNAKE/g;s/\([[ ]\)badger\([] ]\)/\1SNAKE\2/g;s/ badger]/ SNAKE]/g' <<<"badger [badger badger] [badger] badger foobadger badgering mushroom badger"
SNAKE [SNAKE SNAKE] [SNAKE] SNAKE foobadger badgering mushroom SNAKE
2 голосов
/ 06 января 2012
sed -i "s/\bapple\b/FRUIT/g" file

\b соответствует границам слова. Возможно, не полностью переносимый, по крайней мере, не работает на Mac OS X.

И более интересный тест:

$ cat file; sed "s/\bapple\b/FRUIT/g" file
apple apple apple spider tree apple tree applejuice pineapple apple.com etc
FRUIT FRUIT FRUIT spider tree FRUIT tree applejuice pineapple FRUIT.com etc
1 голос
/ 08 января 2012

В одну сторону, используя sed:

sed "s/\([^ ]\)\([ ]\)\([^ ]\)/\1\2\2\3/g; s/\( \|\[\)$a\( \|\]\)/\1$b\2/g; s/\([^ ]\)\([ ]\{2\}\)\([^ ]\)/\1 \3/g" file

Существует три команды подстановки.Объяснение:

s/\([^ ]\)\([ ]\)\([^ ]\)/\1\2\2\3/g      # Duplicate each space character surrounded with non-space 
                                          # characters.
s/\( \|\[\)$a\( \|\]\)/\1$b\2/g           # Substitute content of variable '$a' when just before there is a 
                                          # blank or '[' and just after another space or ']'. Any combination
                                          # of those. And replace with content of variable '$b' and same
                                          # groups of the pattern (\1 and \2).
s/\([^ ]\)\([ ]\{2\}\)\([^ ]\)/\1 \3/g    # Remove a space when found two consecutive surrounded with 
                                          # non-space characters.

Мой тест:

Содержимое файла :

apple spider apple apple spider tree apple tree
apple spider [apple apple spider tree apple] tree
apple spider apple apple spider tree appletree
apple spider apple apple spider tree [apple] tree
apple  spider apple apple apple apple apple spider tree apple tree

Установить переменные:

a="apple"
b="fruit"

Выполнить команду sed:

sed "s/\([^ ]\)\([ ]\)\([^ ]\)/\1\2\2\3/g; s/\( \|\[\)$a\( \|\]\)/\1$b\2/g; s/\([^ ]\)\([ ]\{2\}\)\([^ ]\)/\1 \3/g" file

Результат:

apple spider fruit fruit spider tree fruit tree
apple spider [fruit fruit spider tree fruit] tree
apple spider fruit fruit spider tree appletree
apple spider fruit fruit spider tree [fruit] tree
apple spider fruit fruit fruit fruit fruit spider tree fruit tree

Это не будет работать, если ваш реальный файл имеет другое распределение пробеловили имеет странный формат.В этом случае sed - это ограниченный инструмент, он будет лучше perl или аналогичным с упреждающими взглядами и упущениями.

1 голос
/ 06 января 2012

Рассмотрите возможность использования «смотреть вперед» и «смотреть за спиной»:

s/(?<=[\s\[])apple(?=[\s\]])/FRUIT/g

Демо: http://regexr.com? 2vl8p


Хорошо, я протестировал regexсейчас на моем компьютере и заметил, что смотреть в будущее и смотреть за спиной не работает в стандартном sed, вместо этого вы бы использовали ssed с опцией --regexp-perl:

<b>uname -msrv</b>
Darwin 11.2.0 Darwin Kernel Version 11.2.0: Tue Aug  9 20:54:00 PDT 2011; root:xnu-1699.24.8~1/RELEASE_X86_64 x86_64
<b>ssed --ver</b>
super-sed version 3.62
based on GNU sed version 4.1

Copyright (C) 2003 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE,
to the extent permitted by law.
<b>ssed -R 's/(?<=[\s\[])apple(?=[\s\]])/FRUIT/g'</b>
apple spider apple apple spider tree apple tree
apple spider FRUIT FRUIT spider tree FRUIT tree
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...