Remplacer une sous-chaîne pour une autre chaîne dans le script shell

823

J'ai "J'aime Suzi et Marry" et je veux changer "Suzi" en "Sara".

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

Le résultat doit être comme ceci:

firstString="I love Sara and Marry"
Zincode
la source
18
Je commencerais par laisser tomber doucement Suzi. :)
codesniffer
Ce n'est pas toi c'est moi ! cela aurait dû être la chaîne pour parler à Suzi
GoodSp33d

Réponses:

1360

Pour remplacer la première occurrence d'un modèle par une chaîne donnée, utilisez :${parameter/pattern/string}

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

Pour remplacer toutes les occurrences, utilisez :${parameter//pattern/string}

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

(Ceci est documenté dans le Manuel de référence Bash , §3.5.3 "Expansion des paramètres du shell" .)

Notez que cette fonctionnalité n'est pas spécifiée par POSIX - c'est une extension Bash - donc tous les shells Unix ne l'implémentent pas. Pour la documentation POSIX pertinente, voir The Open Group Technical Standard Base Specifications, Issue 7 , the Shell & Utilities volume, §2.6.2 "Parameter Expansion" .

ruakh
la source
3
@ruakh comment puis-je écrire cette déclaration avec une condition ou. Juste si je veux remplacer Suzi ou Marry par une nouvelle chaîne.
Priyatham51
3
@ Priyatham51: Il n'y a pas de fonction intégrée pour cela. Remplacez simplement l'un, puis l'autre.
ruakh
4
@Bu: Non, parce \nque dans ce contexte se représenterait, pas une nouvelle ligne. Je n'ai pas juste à portée de main Bash maintenant à tester, mais vous devriez être capable d'écrire quelque chose comme $STRING="${STRING/$'\n'/<br />}". (Bien que vous souhaitiez probablement STRING//- remplacer tout - au lieu de juste STRING/.)
ruakh
25
Pour être clair, puisque cela m'a un peu dérouté, la première partie doit être une référence variable. Tu ne peux pas faire echo ${my string foo/foo/bar}. Vous auriez besoininput="my string foo"; echo ${input/foo/bar}
Henrik N
2
@mgutt: Cette réponse renvoie à la documentation pertinente. La documentation n'est pas parfaite - par exemple, au lieu de mentionner //directement, elle mentionne ce qui se passe "Si le modèle commence par ' /'" (ce qui n'est même pas exact, car le reste de cette phrase suppose que le supplément /ne fait pas réellement partie de le modèle) - mais je ne connais pas de meilleure source.
ruakh
208

Cela peut être fait entièrement avec la manipulation de chaîne bash:

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

Cela ne remplacera que la première occurrence; pour les remplacer tous, doublez la première barre oblique:

first="Suzy, Suzy, Suzy"
second="Sara"
first=${first//Suzy/$second}
# first is now "Sara, Sara, Sara"
Kevin
la source
9
Quel est l'intérêt d'avoir une réponse qui duplique celle acceptée, au lieu de la modifier avec l'option de remplacement global? Et en ajoutant que les regexps sont pris en charge, tout en y étant. Et expliquer ce qui se passe avec "Mauvaise substitution". Vous avez assez de points, @Kevin :)
Dan Dascalescu
6
Il semble qu'ils aient tous deux répondu dans la même minute exacte: O
Jamie Hutber
76

Pour Dash, tous les messages précédents ne fonctionnent pas

La shsolution compatible POSIX est:

result=$(echo "$firstString" | sed "s/Suzi/$secondString/")
Roman Kazanovskyi
la source
1
J'ai obtenu ceci: $ echo $ (echo $ firstString | sed 's / Suzi / $ secondString / g') J'aime $ secondString et Marry
Qstnr_La
2
@Qstnr_La utilise des guillemets doubles pour la substitution de variables:result=$(echo $firstString | sed "s/Suzi/$secondString/g")
emc
1
Plus 1 pour montrer également comment produire une sortie vers une variable. Merci!
Chef Pharaoh
2
J'ai corrigé les guillemets simples et ajouté également les guillemets manquants autour de l' echoargument. Il fonctionne de manière trompeuse sans citer avec des chaînes simples, mais se casse facilement sur n'importe quelle chaîne d'entrée non triviale (espacement irrégulier, métacaractères shell, etc.).
tripleee
Dans sh (AWS Codebuild / Ubuntu sh), j'ai trouvé que j'avais besoin d'une seule barre oblique à la fin, pas d'une double. Je vais modifier le commentaire car les commentaires ci-dessus montrent également une seule barre oblique.
Tim
67

essaye ça:

 sed "s/Suzi/$secondString/g" <<<"$firstString"
Kent
la source
19
Vous n'avez pas vraiment besoin de Sed pour cela; Bash supporte nativement ce type de remplacement.
ruakh
12
Je suppose que c'est étiqueté "bash" mais est venu ici parce qu'il fallait quelque chose de simple pour un autre shell. C'est une belle alternative succincte à ce que wiki.ubuntu.com/… donnait l' impression que j'aurais besoin.
natevw
3
Cela fonctionne très bien pour le frêne / tableau de bord ou tout autre sh POSIX.
Yoshua Wuyts
2
J'obtiens l'
Nam G VU
1
Première réponse qui fonctionne avec stdin, idéale pour le pipelining de chaînes.
Dinei
46

Il vaut mieux utiliser bash que sedsi les chaînes ont des caractères RegExp.

echo ${first_string/Suzi/$second_string}

Il est portable pour Windows et fonctionne avec au moins aussi vieux que Bash 3.1.

Pour montrer que vous n'avez pas à vous soucier de vous échapper, tournons ceci:

/home/name/foo/bar

En cela:

~/foo/bar

Mais seulement si /home/namec'est au début. Nous n'avons pas besoin sed!

Étant donné que bash nous donne des variables magiques $PWDet $HOME, nous pouvons:

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

EDIT: Merci pour Mark Haferkamp dans les commentaires pour la note sur la citation / l'évasion ~. *

Notez comment la variable $HOMEcontient des barres obliques mais cela n'a rien cassé.

Pour en savoir plus: Advanced Bash-Scripting Guide .
Si l'utilisation sedest indispensable, assurez-vous d' échapper à chaque personnage .

Camilo Martin
la source
Cette réponse m'a empêché d'utiliser sedla pwdcommande pour éviter de définir une nouvelle variable à chaque $PS1exécution de ma commande personnalisée . Bash fournit-il un moyen plus général que les variables magiques d'utiliser la sortie d'une commande pour remplacer une chaîne? Quant à votre code, j'ai dû échapper au ~pour empêcher Bash de le développer dans $ HOME. De plus, que fait le #dans votre commande?
Mark Haferkamp
1
@MarkHaferkamp Voir ceci à partir du lien "lecture supplémentaire recommandée". A propos de "s'échapper du ~": remarquez comment j'ai cité des trucs. N'oubliez pas de toujours citer des trucs! Et cela ne fonctionne pas seulement pour les variables magiques: toute variable est capable de substitutions, d'obtenir la longueur de chaîne, et plus, dans bash. Félicitations pour avoir essayé votre $PS1jeûne: vous pourriez également être intéressé $PROMPT_COMMANDsi vous êtes plus à l'aise dans un autre langage de programmation et que vous souhaitez coder une invite compilée.
Camilo Martin
Le lien "lecture supplémentaire" explique le "#". Sur Bash 4.3.30, echo "${PWD/#$HOME/~}"ne remplace pas mon $HOMEpar ~. Remplacer ~par \~ou '~'fonctionne. N'importe lequel de ces travaux sur Bash 4.2.53 sur une autre distribution. Pouvez-vous mettre à jour votre message pour citer ou échapper à la ~pour une meilleure compatibilité? Ce que je voulais dire par ma question "variables magiques" était: Puis-je utiliser la substitution de variables de Bash sur, par exemple, la sortie de unamesans l'enregistrer d'abord comme variable? Quant à mon personnel $PROMPT_COMMAND, c'est compliqué .
Mark Haferkamp
@MarkHaferkamp Whoa, vous avez tout à fait raison, ma mauvaise. Met à jour la réponse maintenant.
Camilo Martin
1
@MarkHaferkamp Bash et ses pièges obscurs ...: P
Camilo Martin
19

Si demain vous décidez que vous n'aimez pas Marry, elle peut également être remplacée:

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

Il doit y avoir 50 façons de quitter votre amoureux.

Josh Habdas
la source
9

Puisque je ne peux pas ajouter de commentaire. @ruaka Pour rendre l'exemple plus lisible, écrivez-le comme ceci

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
la source
Jusqu'à ce que j'en vienne à votre exemple, j'avais compris l'ordre dans l'autre sens. Cela a aidé à clarifier ce qui se passe
raghav710
5

Pur Posix méthode shell, qui , contrairement à Roman Kazanovskyi de sedréponse à base n'a pas besoin d'outils externes, à seulement propres natifs de la coquille expansions de paramètres . Notez que les noms de fichiers longs sont minimisés afin que le code s'adapte mieux sur une seule ligne:

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

Production:

I love Sara and Marry

Comment ça fonctionne:

  • Supprimer le plus petit motif de suffixe. "${f%$t*}"renvoie " I love" si le suffixe $t" Suzi*" est dans $f" ". I love Suzi and Marry

  • Mais si t=Zelda, alors "${f%$t*}"ne supprime rien et renvoie la chaîne entière " I love Suzi and Marry".

  • Ceci est utilisé pour tester if $tis in $favec [ "${f%$t*}" != "$f" ]lequel sera évalué à true si la $fchaîne contient " Suzi*" et false sinon.

  • Si le test retourne vrai , construisez la chaîne souhaitée en utilisant Remove Smallest Suffix Pattern ${f%$t*} " I love" et Remove Smallest Prefix Pattern ${f#*$t} " and Marry", avec la 2ème chaîne $s" Sara" entre les deux.

agc
la source
5

Je sais que c'est vieux mais comme personne n'a mentionné l'utilisation de awk:

    firstString="I love Suzi and Marry"
    echo $firstString | awk '{gsub("Suzi","Sara"); print}'
Payam
la source
1
Cette astuce est la voie à suivre si ce que vous essayez de fractionner n'est pas réellement dans une variable mais dans un fichier texte. Merci, @Payam!
Felipe SS Schneider
2
echo [string] | sed "s/[original]/[target]/g"
  • "s" signifie "substitut"
  • "g" signifie "global, toutes les occurrences correspondantes"
Alberto Salvia Novella
la source