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"

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” .


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"

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/")

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"

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 .


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.


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}

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 $t„ Suzi*” 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 $s„ Sara” pomiędzy nimi.


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}'

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”
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.