Как найти и заменить первое совпадение переменной BASH в файле, если замена содержит обратную косую черту? - PullRequest
1 голос
/ 20 марта 2012

Я пытаюсь найти и заменить переменными sed и BASH. Поскольку заменяемое содержимое содержит много символов /, я использовал # вместо /, как обычно используется в sed:

 sed -i "0,\#^$word#s##$replacement#" ./file

Элемент, который я хочу заменить, содержит символ \:

 replacement=$(echo "\macro{30\percent of bears eat fish.}")

Однако, когда я делаю замену, \ всегда исчезает.

  • У меня нет контроля над содержимым в "\macro{30\percent of bears eat fish.}"
  • Если появляется \t, это интерпретируется как отступ, но я просто хочу, чтобы оно выводилось как \t в выводе.

Как найти и заменить на sed, если в тексте замены могут быть найдены \, / или другие символы?

Ответы [ 6 ]

5 голосов
/ 20 марта 2012

Обратные слеши усложняют жизнь

Жизнь всегда становится сложной, когда у вас есть оболочка, и sed оба пытаются интерпретировать обратную косую черту. На самом деле, это становится очень больно.

Давайте рассмотрим простой случай: вы хотите, чтобы sed заменил первую обратную косую черту в строке знаком at:

sed 's/\\/@/' file

Обратите внимание, что нам нужно было указать две обратные косые черты здесь, и поскольку команда была в одинарных кавычках, оболочка не выполняла для нас никакой интерпретации & mdash; слава богу!

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

sed "s/\\\\/@/" file

Почему? Потому что оболочка сначала сканирует строку и видит обратную косую черту и переводит ее в одну обратную косую черту; и затем он видит второй обратный слеш-обратный слеш и переводит его в другой одиночный обратный слеш. Итак, sed увидит две обратные косые черты и сделает то, что вы хотели.


bash-as-bash ведет себя не так, как bash-as-sh

У вас может быть /bin/echo на вашем компьютере (у меня на моем компьютере Mac OS X 10.7.3), который ведет себя не так, как встроенный в bash echo. Я могу получить следующий вывод (от /bin/bash):

$ echo '\\' | sed "s/\\\\/@/"
@\
$ /bin/echo '\\' | sed "s/\\\\/@/"
@\
$ echo '\\' | sed 's/\\\\/@/'
@
$ /bin/echo '\\' | sed 's/\\\\/@/'
@
$ 

Давайте добавим еще один гаечный ключ в работу (у меня болит мозг - как у вас дела?). Я получаю другой ответ от /bin/sh (который очень похож, но не идентичен /bin/bash; разница составляет 64 байта на диске, и оба bash версии 3.2):

$ echo '\\' | sed "s/\\\\/@/"
@
$ /bin/echo '\\' | sed "s/\\\\/@/"
@\
$ echo '\\' | sed 's/\\\\/@/'
\
$ /bin/echo '\\' | sed 's/\\\\/@/'
@
$

Да, есть разница. А теперь я в замешательстве!


В своем обсуждении вы упоминаете использование:

replacement=$(echo "\\\macro{some text}")

Это усложняет жизнь. У вас есть оболочка, обрабатывающая этот текст, и встроенная в bash echo также обрабатывает текст. И если вы используете bash в качестве sh, вы получите один ответ, а если вы используете bash в качестве bash, вы получите другой ответ.

Когда я тестировал bash как sh, я обнаружил:

replacement="\\\\macro{some text}"   # 4 slashes, not 3

Это получает два обратных слэша в $replacement. Вы можете продемонстрировать это, запустив:

$ /bin/echo "$replacement"
\\macro{some text}
$ echo "$replacement"
\macro{some text}
$

Так полезно ... чтобы получить $(echo ...) для получения двух обратных косых черт в выходных данных при последующем echo этом, вам нужно не менее 16 (!) Обратных косых черт в строке:

$ replacement=$(echo "\\\\\\\\\\\\\\\\macro{some text}")
$ echo "$replacement"
\\macro{some text}
$

Осторожно : это относится к bash-as-sh; запустив bash-as-bash, вы получите:

$ replacement=$(echo "\\\\\\\\\\\\\\\\macro{some text}")
$ echo "$replacement"
\\\\\\\\macro{some text
$

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


Тестовый скрипт

Вот тестовый скрипт, который я назвал, с потрясающей оригинальностью и ясностью имени, xx:

#!/bin/bash

echo "happy /word/ today" > file

word="/word/"
/bin/echo "word=<<$word>>"

replacement='\\\\macro{some text}'
/bin/echo "repl=<<$replacement>>"
echo "repl=<<$replacement>>"

/bin/echo sed -e "s#$word#$replacement#" file
sed -e "s#$word#$replacement#" file

echo
replacement="\\\\macro{some text}"
/bin/echo "repl=<<$replacement>>"
echo "repl=<<$replacement>>"

/bin/echo sed -e "s#$word#$replacement#" file
sed -e "s#$word#$replacement#" file


echo
replacement=$(echo "\\\\\\\\macro{some text}")
/bin/echo "$replacement"
echo "$replacement"
echo

replacement=$(echo "\\\\\\\\\\\\\\\\macro{some text}")
/bin/echo "$replacement"
echo "$replacement"

Тестовый скрипт, запускаемый bash-as-bash

Это вывод из bash xx:

$ bash xx
word=<</word/>>
repl=<<\\\\macro{some text}>>
repl=<<\\\\macro{some text}>>
sed -e s#/word/#\\\\macro{some text}# file
happy \\macro{some text} today

repl=<<\\macro{some text}>>
repl=<<\\macro{some text}>>
sed -e s#/word/#\\macro{some text}# file
happy \macro{some text} today

\\\\macro{some text}
\\\\macro{some text}

\\\\\\\\macro{some text}
\\\\\\\\macro{some text}
$ 

Тестовый скрипт, запускаемый bash-as-sh

И это вывод из sh xx:

$ sh xx
word=<</word/>>
repl=<<\\\\macro{some text}>>
repl=<<\\macro{some text}>>
sed -e s#/word/#\\\\macro{some text}# file
happy \\macro{some text} today

repl=<<\\macro{some text}>>
repl=<<\macro{some text}>>
sed -e s#/word/#\\macro{some text}# file
happy \macro{some text} today

\\macro{some text}
\macro{some text}

\\\\macro{some text}
\\macro{some text}
$ 

Где-то среди всего, что является (большей частью) ответом на вашу проблему. Сказать, что я был удивлен, обнаружив, что такая большая разница между bash-as-bash и bash-as-sh, не начинает охватывать территорию.

4 голосов
/ 20 марта 2012

Возможно, ваша проблема не в обратном слэше.Или, по крайней мере, одна из ваших проблем.: -)

В sed, установленном в моей системе, вы можете использовать # в качестве разделителя вашей замены (то есть s#pattern#replacement#), но не в качестве части вашего начального поиска.Таким образом, вам придется использовать что-то вроде:

 sed -i "1,/^$word/s##$replacement#" ./file

Посмотрите мои результаты:

[ghoti@pc ~]$ printf 'one\ntwo\nthree\nfour\nfive\n' | sed '1,/three/d'
four
five
[ghoti@pc ~]$ printf 'one\ntwo\nthree\nfour\nfive\n' | sed '1,#three#d'
sed: 1: "1,#three#d": expected context address
[ghoti@pc ~]$ 

Я нахожусь во FreeBSD, но я почти уверен, что GNU sed работает так жеway.

Это не поможет вам, если в шаблоне есть несовместимые символы, которые вы пытаетесь сопоставить.Вам просто нужно убедиться, что $word является правильно отформатированным регулярным выражением, которое можно использовать в sed.

Что касается обратной косой черты ... если приведенный выше совет доставит вам какую-либо радость, попробуйте простоудвоение любых обратных косых черт в шаблоне заменыНо работайте только с одной проблемой за раз и начните с более простых $replacement с, чтобы убедиться, что ваша базовая нотация sed работает.

ОБНОВЛЕНИЕ:

Кроме того,для удовольствия:

[ghoti@pc ~]$ word="word"
[ghoti@pc ~]$ replacement="\\\macro{some text};"
[ghoti@pc ~]$ printf "Replace a word with some text.\n" | sed "s#$word#$replacement#"
Replace a \macro{some text}; with some text.
[ghoti@pc ~]$ replacement="\\\\macro{some text};"
[ghoti@pc ~]$ printf "Replace a word with some text.\n" | sed "s#$word#$replacement#"
Replace a \macro{some text}; with some text.
[ghoti@pc ~]$ printf "Replace a word with some text.\n" | sed "s/$word/$replacement/"
Replace a \macro{some text}; with some text.
[ghoti@pc ~]$ word="two"
[ghoti@pc ~]$ printf "one\ntwo\nthree\n" | sed "s/^$word/$replacement/"
one
\macro{some text};
three
[ghoti@pc ~]$ 
2 голосов
/ 12 мая 2012
word=$(echo -E "wo\rd")
replacement=$(echo -E "r/e\p")

echo -ne "a\nb\nc\nd\nwo\\\\rd\ne\nf\n" | sed -e "0,\#^${word//\\/\\\\}# s##${replacement//\\/\\\\}#"

Обратите внимание, что у вас все еще будут проблемы с другими символами & и # в строке замены и, конечно, со всеми действительными символами регулярного выражения в строке слова

Ниже приведены регулярные выражения символов

word=$(echo -E "^\$.*[#]wo\rd" | sed 's:[]\[\^\$\.\*#\\]:\\&:g')
replacement=$(echo -E "&#r/e\p" | sed 's:[&#\\]:\\&:g')

echo -ne "a\nb\nc\nd\n^\$.*[#]wo\\\\rd\ne\nf\n" | sed -e "0,\#^${word}# s##${replacement}#"
2 голосов
/ 20 марта 2012

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

sed -i "0,\#^$word#s##$(printf '%q' $replacement)#" ./file
1 голос
/ 08 мая 2012

Попробуйте это

Вход

/home/Scripts/dummyrun 

Команда Unix

$> sed -i "s%/home/Scripts/dummyrun%Newpath%g" Input

Выход

Откройте файл ввода, и изменения будут автоматически отражены.

 $> cat Input 
    Newpath

Приведенная выше команда заменяет / home / Scripts / dummyrun на Newpath в файле ввода.

1 голос
/ 20 марта 2012

Попробуйте (если я правильно понимаю вашу проблему):

R="macro{some text}"
sed -i "0,\#^$word#s##\\$R#" ./file
...