Замініть одну підрядку на іншу рядок у скрипті оболонки


821

У мене є "Я люблю Сузі і виходжу заміж" і хочу змінити "Сузі" на "Сару".

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

Результат повинен бути таким:

firstString="I love Sara and Marry"

15
Я б почав, обережно опустивши Сузі. :)
codeniffer

Це не ти, це я! це повинно було бути
ланкою

Відповіді:


1358

Щоб замінити перше виникнення шаблону заданим рядком, використовуйте :${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 див . Технічні стандартні бази відкритої групи, випуск 7 , том Shell & Utilities , §2.6.2 "Розширення параметрів" .


3
@ruakh як мені написати цю заяву з умовою або. Так само, якщо я хочу замінити Suzi або Marry на новий рядок.
Priyatham51

3
@ Priyatham51: Для цього немає вбудованої функції. Просто замініть то одне, то інше.
ruakh

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}
Генрік Н

2
@mgutt: Ця відповідь посилається на відповідну документацію. Документація не є досконалою - наприклад, замість //прямої згадки , вона згадує, що відбувається "Якщо шаблон починається з /" "(що навіть не є точним, оскільки решта цього речення передбачає, що додатково /насправді не є частиною модель) - але я не знаю жодного кращого джерела.
ruakh

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"

9
Який сенс мати відповідь, що дублює прийняте, замість того, щоб редагувати прийнятий за допомогою глобальної опції заміни? І додамо, що регулярні вирівнювання підтримуються, перебуваючи при цьому. І пояснення того, що сталося з "Поганою заміною". У вас вистачає балів, @Kevin :)
Дан Даскалеску

6
Схоже, вони обоє відповіли точно в одну і ту ж хвилину: О
Джеймі Хатбер

76

Для Dash всі попередні повідомлення не працюють

Рішення, shсумісне з POSIX :

result=$(echo "$firstString" | sed "s/Suzi/$secondString/")

1
Я отримав це: $ echo $ (echo $ firstString | sed 's / Suzi / $ secondString / g') Я люблю $ secondString та
одружуйтесь

2
@Qstnr_La використовуйте подвійні лапки для змінної підстановки:result=$(echo $firstString | sed "s/Suzi/$secondString/g")
emc

1
Плюс 1 - показувати, як виводити змінну. Дякую!
Фараон шеф-кухаря

2
Я виправив єдині лапки, а також додав відсутні echoаргументи навколо аргументу. Він оманливо працює без цитування простих рядків, але легко переривається на будь-який нетривіальний вхідний рядок (нерегулярний інтервал, метахарактеристики оболонки тощо).
тріплей

У sh (AWS Codebuild / Ubuntu sh) я виявив, що мені потрібна одна коса риса в кінці, а не подвійна. Я збираюсь відредагувати коментар, оскільки коментарі, наведені вище, також показують одну косу рису.
Тім

67

спробуйте це:

 sed "s/Suzi/$secondString/g" <<<"$firstString"

19
Ви для цього справді не потребуєте Sed; Bash підтримує подібну заміну на самому початку.
ruakh

12
Я думаю, це позначено "баш", але прийшов сюди, тому що для іншої оболонки потрібно щось просте Це приємна альтернатива тому, що wiki.ubuntu.com/… зробило це так, як мені потрібно.
natevw

3
Це чудово підходить для золи / деш або будь-якого іншого POSIX sh.
Йошуа Вуйтс

2
Я отримую помилку sed: -e вираз №1, char 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/\~}"

EDIT: Дякую Марку Хаферкампу в коментарях до примітки про цитування / втечу ~. *

Зверніть увагу, як змінна $HOMEмістить косої риси, але це нічого не порушило.

Подальше читання: Розширений посібник із написання сценарію .
Якщо використання sedє обов'язковим, обов'язково уникайте кожного персонажа .


Ця відповідь зупинив мене від використання sedз pwdкомандою , щоб уникнути визначення нової змінної кожен раз , коли мої власні $PS1пробіги. Чи пропонує Bash більш загальний спосіб, ніж магічні змінні, використовувати вихід команди для заміни рядків? Що стосується вашого коду, я повинен був уникнути, ~щоб утримати Bash від його розширення на $ HOME. Крім того, що робить #ваша команда?
Марк Хаферкамп

1
@MarkHaferkamp Дивіться це за посиланням "рекомендується для подальшого читання". Про "втечу ~": зауважте, як я цитував речі. Не забудьте завжди цитувати речі! І це не працює лише для магічних змінних: будь-яка змінна здатна замінювати, отримувати довжину рядка та інше в межах bash. Поздоровлення про те, що ви намагаєтеся $PS1швидко: вам також може бути цікаво, $PROMPT_COMMANDчи вам зручніше в іншій мові програмування та хочете кодувати складений підказку.
Каміло Мартін

Посилання "подальше читання" пояснює "#". На Баш 4.3.30, echo "${PWD/#$HOME/~}"не замінить мій $HOMEз ~. Заміна ~з \~або '~'роботи. Будь-яка з цих робіт на Bash 4.2.53 на іншому дистрибутиві. Чи можете ви, будь ласка, оновити свою публікацію, щоб запропонувати цитату чи уникнути ~кращої сумісності? Що я мав на увазі під своїм "магічним змінним" питанням: чи можу я використовувати підстановку змінної Баша на, наприклад, на виході unameбез збереження спочатку як змінної? Що стосується моєї особистої $PROMPT_COMMAND, то це складно .
Марк Хаферкамп

@MarkHaferkamp О, ти абсолютно прав, моя погана. Відповідь буде оновлено зараз.
Каміло Мартін

1
@MarkHaferkamp Bash та її незрозумілі підводні камені ...: P
Camilo Martin

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" ]яким буде оцінено як істинне, якщо $fрядок містить " Suzi*", а помилково, якщо ні.

  • Якщо тест повернеться істинним , побудуйте потрібний рядок за допомогою " " Найменшого шаблону суфіксів ${f%$t*} " I love" та " Видалити найменший шаблон префікса ${f#*$t} " and Marry", з 2-й рядком $s" Sara"між ними.


5

Я знаю, що це старе, але оскільки ніхто не згадував про використання awk:

    firstString="I love Suzi and Marry"
    echo $firstString | awk '{gsub("Suzi","Sara"); print}'

1
Цей трюк - це шлях, якщо те, що ви намагаєтеся розділити, насправді не в змінній, а в текстовому файлі. Дякую, @Payam!
Феліпе СС Шнайдер

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.