Zamień jeden podciąg na inny ciąg w skrypcie powłoki

821

Mam „I love Suzi and Marry” i chcę zmienić „Suzi” na „Sara”.

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

Wynik musi być taki:

firstString="I love Sara and Marry"
Zincode
źródło
15
Zacznę od łagodnego upuszczenia Suzi. :)
codeniffer
To nie ty to ja ! powinien to być ciąg rozmowy z Suzi
GoodSp33d

Odpowiedzi:

1358

Aby zastąpić pierwsze wystąpienie wzorca danym ciągiem, użyj :${parameter/pattern/string}

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

Aby zastąpić wszystkie wystąpienia, użyj :${parameter//pattern/string}

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

(Jest to opisane w podręczniku atakujących odniesienia , §3.5.3 „Shell parametrów Expansion” ).

Zauważ, że ta funkcja nie jest określona przez POSIX - jest to rozszerzenie Bash - więc nie wszystkie powłoki Unix ją implementują. Aby zapoznać się z odpowiednią dokumentacją POSIX, zobacz Podstawową specyfikację standardu technicznego Open Group, wydanie 7 , tom Shell & Utilities , § 2.6.6 „Rozszerzanie parametrów” .

ruakh
źródło
3
@ruakh jak napisać to oświadczenie z warunkiem lub. Tylko jeśli chcę zastąpić Suzi lub Marry nowym ciągiem.
Priyatham51
3
@ Priyatham51: Nie ma do tego wbudowanej funkcji. Wystarczy wymienić jeden, a potem drugi.
ruakh
4
@Bu: Nie, ponieważ \nw tym kontekście reprezentowałby siebie, a nie nowy wiersz. Nie mam Bash poręczny prawo teraz do testu, ale powinieneś być w stanie napisać coś podobnego $STRING="${STRING/$'\n'/<br />}". (Chociaż prawdopodobnie chcesz STRING//- zamień wszystko - zamiast tylko STRING/.)
ruakh
25
Żeby było jasne, ponieważ trochę mnie to pomieszało, pierwsza część musi być zmiennym odniesieniem. Nie można tego zrobić echo ${my string foo/foo/bar}. Potrzebujeszinput="my string foo"; echo ${input/foo/bar}
Henrik N
2
@mgutt: Ta odpowiedź prowadzi do odpowiedniej dokumentacji. Dokumentacja nie jest doskonała - na przykład, zamiast //bezpośrednio wspomnieć , wspomina, co się dzieje „Jeśli wzorzec zaczyna się od /„ ”(co nie jest nawet dokładne, ponieważ reszta tego zdania zakłada, że ​​dodatkowe /nie jest tak naprawdę częścią wzór) - ale nie znam lepszego źródła.
ruakh
208

Można to zrobić całkowicie za pomocą manipulacji ciągiem bash:

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

To zastąpi tylko pierwsze wystąpienie; aby zamienić je wszystkie, podwój pierwszy ukośnik:

first="Suzy, Suzy, Suzy"
second="Sara"
first=${first//Suzy/$second}
# first is now "Sara, Sara, Sara"
Kevin
źródło
9
Po co mieć odpowiedź, która powiela zaakceptowaną, zamiast edytować zaakceptowaną z opcją globalnej zamiany? I dodając, że wyrażenia regularne są obsługiwane, podczas gdy w tym. I wyjaśnienie, co słychać w „złym zastąpieniu”. Masz wystarczającą liczbę punktów, @Kevin :)
Dan Dascalescu
6
Wygląda na to, że oboje odpowiedzieli dokładnie w tej samej minucie: O
Jamie Hutber
76

W przypadku Dasha wszystkie poprzednie posty nie działają

Rozwiązaniem shzgodnym z POSIX jest:

result=$(echo "$firstString" | sed "s/Suzi/$secondString/")
Roman Kazanowski
źródło
1
Mam to: $ echo $ (echo $ firstString | sed 's / Suzi / $ secondString / g') Uwielbiam $ secondString i Marry
Qstnr_La
2
@Qstnr_La używaj podwójnych cudzysłowów do podstawiania zmiennych:result=$(echo $firstString | sed "s/Suzi/$secondString/g")
emc
1
Plus 1 za pokazanie, jak wyprowadzać dane do zmiennej. Dzięki!
Szef kuchni Faraon
2
Naprawiłem pojedyncze cytaty, a także dodałem brakujące cytaty wokół echoargumentu. Zwodniczo działa bez cytowania przy użyciu prostych ciągów, ale łatwo psuje dowolny nietrywialny ciąg wejściowy (nieregularne odstępy, metaznaki powłoki itp.).
tripleee
W sh (AWS Codebuild / Ubuntu sh) stwierdziłem, że potrzebuję jednego ukośnika na końcu, a nie podwójnego. Zamierzam edytować komentarz, ponieważ powyższe komentarze pokazują również pojedynczy ukośnik.
Tim
67

Spróbuj tego:

 sed "s/Suzi/$secondString/g" <<<"$firstString"
Kent
źródło
19
Tak naprawdę nie potrzebujesz do tego Sed; Bash obsługuje ten rodzaj wymiany natywnie.
ruakh
12
Myślę, że jest to oznaczone jako „bash”, ale przyszło tutaj, ponieważ potrzebowałem czegoś prostego dla innej powłoki. To miła, zwięzła alternatywa dla tego, co wiki.ubuntu.com/... sprawiło, że wyglądało na to, że potrzebuję.
natevw
3
Działa to doskonale dla ash / dash lub dowolnego innego POSIX sh.
Yoshua Wuyts
2
Dostaję błąd sed: -e wyrażenie # 1, char 9: nieznana opcja na `s
Nam G VU
1
Pierwsza odpowiedź, która działa ze stdin, świetna do ciągów potokowych.
Dinei
46

Lepiej jest używać bash, niż sedjeśli łańcuchy zawierają znaki RegExp.

echo ${first_string/Suzi/$second_string}

Jest przenośny dla systemu Windows i działa z co najmniej tak starym jak Bash 3.1.

Aby pokazać, że nie musisz się zbytnio przejmować ucieczką, obróćmy to:

/home/name/foo/bar

Zaangażowany w to:

~/foo/bar

Ale tylko jeśli /home/namejest na początku. Nie potrzebujemy sed!

Biorąc pod uwagę, że bash daje nam magiczne zmienne $PWDi $HOMEmożemy:

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

EDYCJA: Dzięki za Mark Haferkamp w komentarzach do notatki na temat cytowania / ucieczki ~. *

Zauważ, że zmienna $HOMEzawiera ukośniki, ale to niczego nie zepsuło.

Dalsza lektura: Przewodnik po zaawansowanych skryptach bashowych .
Jeśli używanie sedjest koniecznością, pamiętaj o ucieczce każdej postaci .

Camilo Martin
źródło
Ta odpowiedź zatrzymał mnie od korzystania sedz pwdpoleceniem, aby uniknąć definiowania nowej zmiennej za każdym razem moje własne $PS1przejazdy. Czy Bash zapewnia bardziej ogólny sposób niż zmienne magiczne wykorzystanie wyjścia polecenia do zamiany łańcucha? Jeśli chodzi o twój kod, musiałem uciec, ~aby Bash nie rozwinął go do $ HOME. Co również robi #twoje polecenie?
Mark Haferkamp
1
@MarkHaferkamp Zobacz to z linku „zalecane dalsze czytanie”. O „ucieczce ~”: zauważ, jak cytowałem różne rzeczy. Pamiętaj, aby zawsze cytować różne rzeczy! I to nie działa tylko w przypadku zmiennych magicznych: każda zmienna jest zdolna do podstawiania, uzyskiwania długości łańcucha i więcej w ramach bash. Gratulujemy $PS1postu: możesz być także zainteresowany, $PROMPT_COMMANDjeśli czujesz się lepiej w innym języku programowania i chcesz zakodować skompilowane zapytanie.
Camilo Martin
Link „dalsze czytanie” wyjaśnia „#”. Na Bash 4.3.30, echo "${PWD/#$HOME/~}"nie zastąpią mi $HOMEsię ~. Wymiana ~z \~lub '~'prace. Każda z nich działa na Bash 4.2.53 w innej dystrybucji. Czy możesz zaktualizować swój post, aby zacytować lub uciec przed ~lepszą kompatybilnością? Moje pytanie „magiczne zmienne” miałem na myśli: czy mogę użyć podstawienia zmiennej Basha, np. Na wyjściu unamebez zapisywania go jako zmiennej? Jeśli chodzi o mój osobisty $PROMPT_COMMAND, to skomplikowane .
Mark Haferkamp
@MarkHaferkamp Zaraz, masz całkowitą rację, mój zły. Zaktualizuje odpowiedź teraz.
Camilo Martin
1
@MarkHaferkamp Bash i jego niejasne pułapki ...: P
Camilo Martin
19

Jeśli jutro zdecydujesz, że nie kochasz Marry, ona również może zostać zastąpiona:

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

Musi być 50 sposobów na opuszczenie kochanka.

Josh Habdas
źródło
9

Ponieważ nie mogę dodać komentarza. @ruaka Aby uczynić przykład bardziej czytelnym, napisz go w ten sposób

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}
Nate Revo
źródło
Dopóki nie przyszedłem do twojego przykładu, zrozumiałem kolejność na odwrót. Pomogło to wyjaśnić, co się dzieje
raghav710,
5

Czysta POSIX metoda powłoki, które w odróżnieniu od Roman Kazanovskyi „s sedopartym odpowiedź potrzebuje żadnych zewnętrznych narzędzi, zaledwie ojczystym skorupy za ekspansje parametrów . Zauważ, że długie nazwy plików są zminimalizowane, więc kod lepiej pasuje do jednej linii:

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

Wynik:

I love Sara and Marry

Jak to działa:

  • Usuń najmniejszy wzór sufiksu. "${f%$t*}"zwraca „ I love”, jeśli sufiks $tSuzi*” jest w $f„ ”. I love Suzi and Marry

  • Ale jeśli t=Zelda, to "${f%$t*}"nic nie usuwa i zwraca cały ciąg „ I love Suzi and Marry”.

  • Służy do testowania, czy $tjest w, $fz [ "${f%$t*}" != "$f" ]którym ma wartość true, jeśli $fciąg zawiera „ Suzi*”, a fałsz, jeśli nie.

  • Jeśli test zwróci wartość true , skonstruuj żądany ciąg znaków, używając opcji Usuń najmniejszy wzorzec sufiksu ${f%$t*}I love” i Usuń najmniejszy wzorzec prefiksu ${f#*$t}and Marry”, z drugim ciągiem $sSara” pomiędzy nimi.

agc
źródło
5

Wiem, że to stare, ale ponieważ nikt nie wspomniał o używaniu awk:

    firstString="I love Suzi and Marry"
    echo $firstString | awk '{gsub("Suzi","Sara"); print}'
Payam
źródło
1
Ta sztuczka jest dobrym rozwiązaniem, jeśli to, co próbujesz podzielić, nie jest tak naprawdę zmienną, ale plikiem tekstowym. Dzięki, @Payam!
Felipe SS Schneider
2
echo [string] | sed "s/[original]/[target]/g"
  • „s” oznacza „substytut”
  • „g” oznacza „globalne, wszystkie pasujące wystąpienia”
Alberto Salvia Novella
źródło