Заменить одну подстроку на другую строку в сценарии оболочки

823

У меня есть «Я люблю Сьюзи и Жениться», и я хочу изменить «Сьюзи» на «Сара».

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
# do something...

Результат должен быть таким:

firstString="I love Sara and Marry"
Zincode
источник
19
Я бы начал с того, что осторожно подвел Сьюзи. :)
Codesniffer
Это не ты, это я ! это должна была быть строка для разговора с Сьюзи
GoodSp33d

Ответы:

1360

Чтобы заменить первое вхождение шаблона данной строкой, используйте :${parameter/pattern/string}

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
echo "${firstString/Suzi/$secondString}"    
# prints 'I love Sara and Marry'

Чтобы заменить все вхождения, используйте :${parameter//pattern/string}

message='The secret code is 12345'
echo "${message//[0-9]/X}"           
# prints 'The secret code is XXXXX'

(Это отражено в в Bash Reference Manual , §3.5.3 "Shell Параметр Expansion" .)

Обратите внимание, что эта функция не указана в POSIX - это расширение Bash - поэтому не все оболочки Unix реализуют ее. Соответствующую документацию POSIX см. В разделе Базовые технические спецификации стандарта Open Group, выпуск 7 , том Shell & Utilities , §2.6.2 «Расширение параметров» .

ruakh
источник
3
@ruakh как мне написать это утверждение с условием или. Просто, если я хочу заменить Сьюзи или Жениться на новую строку.
Priyatham51
3
@ Priyatham51: для этого нет встроенной функции. Просто замените одно, затем другое.
Руах
4
@Bu: Нет, потому что \nв этом контексте будет представлять себя, а не перевод строки. Сейчас у меня нет возможности использовать Bash для тестирования, но вы должны написать что-то вроде $STRING="${STRING/$'\n'/<br />}". (Хотя вы, вероятно, хотите STRING//- заменить все - вместо просто STRING/.)
ruakh
25
Чтобы было ясно, поскольку это немного смутило меня, первая часть должна быть ссылкой на переменную. Вы не можете сделать echo ${my string foo/foo/bar}. Тебе понадобитсяinput="my string foo"; echo ${input/foo/bar}
Henrik N
2
@mgutt: Этот ответ ссылается на соответствующую документацию. Документация не идеальна - например, вместо //непосредственного упоминания , в ней упоминается, что происходит, «если шаблон начинается с /« »(что даже не является точным, поскольку остальная часть этого предложения предполагает, что дополнительное на /самом деле не является частью шаблон) - но я не знаю лучшего источника.
Руах
208

Это можно сделать полностью с помощью манипуляции с bash:

first="I love Suzy and Mary"
second="Sara"
first=${first/Suzy/$second}

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

first="Suzy, Suzy, Suzy"
second="Sara"
first=${first//Suzy/$second}
# first is now "Sara, Sara, Sara"
Kevin
источник
9
Какой смысл иметь ответ, который дублирует принятый, а не редактировать принятый с глобальной опцией замены? И добавив, что регулярные выражения поддерживаются, пока у него. И объясняя, что случилось с «Плохой заменой». У тебя достаточно очков, @Kevin :)
Дан Даскалеску
6
Похоже, что они оба ответили в одну и ту же минуту: O
Джейми Хатбер,
76

Для Dash все предыдущие посты не работают

POSIX- shсовместимое решение:

result=$(echo "$firstString" | sed "s/Suzi/$secondString/")
Роман Казановский
источник
1
Я получил это: $ echo $ (echo $ firstString | sed 's / Suzi / $ secondString / g') Я люблю $ secondString и Marry
Qstnr_La
2
@Qstnr_La использует двойные кавычки для подстановки переменных:result=$(echo $firstString | sed "s/Suzi/$secondString/g")
emc
1
Плюс 1, показывающий, как выводить переменную. Спасибо!
шеф-повар фараона
2
Я исправил одинарные кавычки, а также добавил недостающие кавычки вокруг echoаргумента. Он обманчиво работает без кавычек с простыми строками, но легко разбивается на любую нетривиальную входную строку (неправильный интервал, метасимволы оболочки и т. Д.).
tripleee
В sh (AWS Codebuild / Ubuntu sh) я обнаружил, что в конце мне нужна одна косая черта, а не двойная. Я собираюсь редактировать комментарий, так как комментарии выше также показывают одну косую черту.
Тим
67

попробуй это:

 sed "s/Suzi/$secondString/g" <<<"$firstString"
Кент
источник
19
Вам на самом деле не нужен Сед для этого; Bash изначально поддерживает такую ​​замену.
ruakh
12
Я думаю, это помечено как "bash", но пришло сюда, потому что нужно что-то простое для другой оболочки. Это хорошая краткая альтернатива тому, что на wiki.ubuntu.com/… выглядело так, как будто мне нужно.
natevw
3
Это прекрасно работает для пепла / тире или любого другого POSIX sh.
Йошуа Вуйц
2
Я получаю ошибку sed: -e выражение # 1, символ 9: неизвестный параметр `s
Nam G VU
1
Первый ответ, который работает с stdin, отлично подходит для конвейерной обработки строк.
Диней
46

Лучше использовать bash, чем sedесли в строках есть символы RegExp.

echo ${first_string/Suzi/$second_string}

Он переносим на Windows и работает по крайней мере со старым, как Bash 3.1.

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

/home/name/foo/bar

В это:

~/foo/bar

Но только если /home/nameэто в начале. Нам не нужно sed!

Учитывая, что bash дает нам волшебные переменные $PWDи $HOMEмы можем:

echo "${PWD/#$HOME/\~}"

РЕДАКТИРОВАТЬ: Спасибо за Марк Haferkamp в комментариях за примечание о цитировании / экранирование ~. *

Обратите внимание, что переменная $HOMEсодержит косые черты, но это ничего не сломало.

Дополнительная информация: Расширенное руководство по написанию сценариев .
Если использование sedявляется обязательным, обязательно избегайте каждого символа .

Камило Мартин
источник
Этот ответ остановил меня от использования sedс pwdкомандой , чтобы избежать определения новой переменной каждый раз , когда мои собственные $PS1пробеги. Предоставляет ли Bash более общий способ, чем магические переменные, использовать вывод команды для замены строки? Что касается вашего кода, мне пришлось избежать того, ~чтобы Bash не расширил его до $ HOME. Кроме того, что делает #в вашей команде?
Марк Хаферкамп
1
@MarkHaferkamp См. Ссылку «Рекомендуется дальнейшее чтение». Про «побег ~»: обратите внимание, как я цитировал материал. Не забывайте всегда цитировать вещи! И это работает не только для магических переменных: любая переменная способна к подстановкам, получению длины строки и многим другим в bash. Поздравляю с попыткой $PS1быстро: вам также может быть интересно, $PROMPT_COMMANDесли вам удобнее использовать другой язык программирования и вы хотите написать скомпилированное приглашение.
Камило Мартин
Ссылка «дальнейшее чтение» объясняет «#». На Bash 4.3.30 echo "${PWD/#$HOME/~}"не заменяет мой $HOMEна ~. Замена ~на \~или '~'работает. Любая из них работает на Bash 4.2.53 в другом дистрибутиве. Можете ли вы обновить свой пост, чтобы процитировать или избежать ~для лучшей совместимости? Под вопросом «магические переменные» я имел в виду следующее: могу ли я использовать подстановку переменных в Bash, например, на выходе, unameне сохраняя его сначала как переменную? Что касается моего личного $PROMPT_COMMAND, это сложно .
Марк Хаферкамп
@MarkHaferkamp Вау, ты совершенно прав, мой плохой. Обновлю ответ сейчас.
Камило Мартин
1
@MarkHaferkamp Bash и его неясные подводные камни ...: P
Камило Мартин
19

Если завтра вы решите, что не любите замуж, то ее тоже можно заменить:

today=$(</tmp/lovers.txt)
tomorrow="${today//Suzi/Sara}"
echo "${tomorrow//Marry/Jesica}" > /tmp/lovers.txt

Должно быть 50 способов расстаться с любовником.

Джош Хабдас
источник
9

Поскольку я не могу добавить комментарий. @ruaka Чтобы сделать пример более читабельным, напишите это так

full_string="I love Suzy and Mary"
search_string="Suzy"
replace_string="Sara"
my_string=${full_string/$search_string/$replace_string}
or
my_string=${full_string/Suzy/Sarah}
Нейт Рево
источник
Пока я пришел к вашему примеру, я понял порядок наоборот. Это помогло прояснить, что происходит
raghav710
5

Чистый POSIX метод оболочки, который в отличии от Roman Kazanovskyi «s sed-На ответа не требует никаких внешних инструментов, только собственные родные оболочечных расширения параметров . Обратите внимание, что длинные имена файлов сведены к минимуму, поэтому код лучше помещается в одну строку:

f="I love Suzi and Marry"
s=Sara
t=Suzi
[ "${f%$t*}" != "$f" ] && f="${f%$t*}$s${f#*$t}"
echo "$f"

Вывод:

I love Sara and Marry

Как это работает:

  • Удалите самый маленький шаблон суффикса. "${f%$t*}"возвращает " I love", если суффикс $t" Suzi*" находится в $f" ". I love Suzi and Marry

  • Но если t=Zelda, то "${f%$t*}"ничего не удаляет и возвращает всю строку " I love Suzi and Marry".

  • Это используется для проверки, если $tнаходится в $fс, [ "${f%$t*}" != "$f" ]который будет иметь значение true, если $fстрока содержит " Suzi*" и false, если нет.

  • Если тест возвращает истинный , построить нужную строку , используя Удалить Наименьший суффиксов Pattern ${f%$t*} « I love» и удалить Наименьший префикс Pattern ${f#*$t} « and Marry», с 2 - ой строке $s« Sara» между ними.

АРУ
источник
5

Я знаю, что это старый, но так как никто не упомянул об использовании awk:

    firstString="I love Suzi and Marry"
    echo $firstString | awk '{gsub("Suzi","Sara"); print}'
Payam
источник
1
Этот трюк - путь, если то, что вы пытаетесь разделить, на самом деле не в переменной, а в текстовом файле. Спасибо, @Payam!
Фелипе Шнайдер
2
echo [string] | sed "s/[original]/[target]/g"
  • «с» означает «заменить»
  • «g» означает «глобальный, все совпадающие вхождения»
Альберто Сальвия Новелла
источник