Reemplace una subcadena por otra cadena en el script de shell

823

Tengo "Amo a Suzi y me caso" y quiero cambiar "Suzi" a "Sara".

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

El resultado debe ser así:

firstString="I love Sara and Marry"
Zincode
fuente
18
Empezaría por decepcionar a Suzi suavemente. :)
codeniffer
No eres tu, soy yo ! esa debería haber sido la cadena para hablar con Suzi
GoodSp33d

Respuestas:

1360

Para reemplazar la primera aparición de un patrón con una cadena dada, use :${parameter/pattern/string}

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

Para reemplazar todas las ocurrencias, use :${parameter//pattern/string}

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

(Esto está documentado en el Manual de referencia de Bash , §3.5.3 "Expansión de parámetros de shell" ).

Tenga en cuenta que POSIX no especifica esta característica, es una extensión Bash, por lo que no todos los shells de Unix la implementan. Para obtener la documentación relevante de POSIX, consulte las especificaciones básicas de la norma técnica de Open Group, número 7 , el volumen Shell y utilidades , §2.6.2 "Expansión de parámetros" .

ruakh
fuente
3
@ruakh, ¿cómo escribo esta declaración con una condición o? Solo si quiero reemplazar a Suzi o Marry con una nueva cadena.
Priyatham51
3
@ Priyatham51: No hay una función incorporada para eso. Simplemente reemplace uno, luego el otro.
ruakh
44
@Bu: No, porque \nen ese contexto se representaría a sí mismo, no a una nueva línea. No tengo Bash útil en este momento para la prueba, pero usted debe ser capaz de escribir algo como: $STRING="${STRING/$'\n'/<br />}". (Aunque probablemente quieras STRING//- reemplazar todo - en lugar de solo STRING/)
ruakh
25
Para ser claros, ya que esto me confundió un poco, la primera parte tiene que ser una referencia variable. No se puede hacer echo ${my string foo/foo/bar}. Necesitaríasinput="my string foo"; echo ${input/foo/bar}
Henrik N
2
@mgutt: esta respuesta enlaza con la documentación relevante. La documentación no es perfecta; por ejemplo, en lugar de mencionar //directamente, menciona lo que sucede "Si el patrón comienza con ' /'" (lo cual ni siquiera es exacto, ya que el resto de esa oración supone que el extra /no es realmente parte de el patrón), pero no conozco ninguna fuente mejor.
Ruakh
208

Esto se puede hacer completamente con la manipulación de cadenas bash:

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

Eso reemplazará solo la primera ocurrencia; para reemplazarlos a todos, doble la primera barra:

first="Suzy, Suzy, Suzy"
second="Sara"
first=${first//Suzy/$second}
# first is now "Sara, Sara, Sara"
Kevin
fuente
99
¿Cuál es el punto de tener una respuesta que duplique la aceptada, en lugar de editar la aceptada con la opción de reemplazo global? Y agregando que las expresiones regulares son compatibles, mientras lo hace. Y explicando qué pasa con "Mala sustitución". Tienes suficientes puntos, @Kevin :)
Dan Dascalescu
66
Parece que ambos respondieron exactamente en el mismo minuto: O
Jamie Hutber
76

Para Dash, todas las publicaciones anteriores no funcionan

La shsolución compatible con POSIX es:

result=$(echo "$firstString" | sed "s/Suzi/$secondString/")
Roman Kazanovskyi
fuente
1
Obtuve esto: $ echo $ (echo $ firstString | sed 's / Suzi / $ secondString / g') Me encanta $ secondString y Casarme
Qstnr_La
2
@Qstnr_La usa comillas dobles para la sustitución de variables:result=$(echo $firstString | sed "s/Suzi/$secondString/g")
emc
1
Además de 1 para mostrar cómo enviar a una variable también. ¡Gracias!
Chef Faraón el
2
Arreglé las comillas simples y también agregué las comillas faltantes alrededor del echoargumento. Funciona engañosamente sin citar con cadenas simples, pero se rompe fácilmente en cualquier cadena de entrada no trivial (espaciado irregular, metacaracteres de shell, etc.).
tripleee
En sh (AWS Codebuild / Ubuntu sh) descubrí que necesito una barra oblicua al final, no una doble. Voy a editar el comentario ya que los comentarios anteriores también muestran una barra inclinada.
Tim
67

prueba esto:

 sed "s/Suzi/$secondString/g" <<<"$firstString"
Kent
fuente
19
En realidad no necesitas Sed para esto; Bash admite este tipo de reemplazo de forma nativa.
ruakh
12
Supongo que esto está etiquetado como "bash" pero vino aquí porque necesitaba algo simple para otro shell. Esta es una buena alternativa sucinta a lo que wiki.ubuntu.com/… hizo que pareciera que lo necesitaría.
natevw
3
Esto funciona muy bien para ash / dash o cualquier otro POSIX sh.
Yoshua Wuyts
2
Me sale el error sed: -e expresión # 1, char 9: opción desconocida para `s
Nam G VU
1
Primera respuesta que funciona con stdin, ideal para canalizar cadenas.
Dinei
46

Es mejor usar bash que sedsi las cadenas tienen caracteres RegExp.

echo ${first_string/Suzi/$second_string}

Es portátil para Windows y funciona con al menos tan antiguo como Bash 3.1.

Para mostrar que no necesita preocuparse demasiado por escapar, cambiemos esto:

/home/name/foo/bar

Dentro de esto:

~/foo/bar

Pero solo si /home/namees al principio. No necesitamos sed!

Dado que bash nos da variables mágicas $PWDy $HOMEpodemos:

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

EDITAR: Gracias por Mark Haferkamp en los comentarios por la nota sobre citas / escape ~. *

Observe cómo la variable $HOMEcontiene barras diagonales, pero esto no rompió nada.

Lectura adicional: Guía avanzada de secuencias de comandos Bash .
Si usar sedes imprescindible, asegúrate de escapar de todos los personajes .

Camilo Martin
fuente
Esta respuesta me impidió usar sedel pwdcomando para evitar definir una nueva variable cada vez que se $PS1ejecuta mi personalizado . ¿Bash proporciona una forma más general que las variables mágicas para usar la salida de un comando para el reemplazo de cadenas? En cuanto a su código, tuve que escapar ~para evitar que Bash lo expandiera a $ HOME. Además, ¿qué hace el #comando?
Mark Haferkamp
1
@ MarkHaferkamp Vea esto desde el enlace "lectura adicional recomendada". Sobre "escapar de la ~": observe cómo cité cosas. ¡Recuerda siempre citar cosas! Y esto no solo funciona para las variables mágicas: cualquier variable es capaz de sustituciones, obtener longitud de cadena y más, dentro de bash. Felicidades por intentar tu $PS1ayuno: también te puede interesar $PROMPT_COMMANDsi estás más cómodo con otro lenguaje de programación y quieres codificar un mensaje compilado.
Camilo Martin
El enlace "lectura adicional" explica el "#". En Bash 4.3.30, echo "${PWD/#$HOME/~}"no reemplaza mi $HOMEcon ~. Reemplazar ~con \~o '~'funciona. Cualquiera de estos funciona en Bash 4.2.53 en otra distribución. ¿Puedes actualizar tu publicación para cotizar o escapar ~para una mejor compatibilidad? Lo que quise decir con mi pregunta de "variables mágicas" fue: ¿Puedo usar la sustitución de variables de Bash en, por ejemplo, la salida de unamesin guardarla primero como una variable? En cuanto a mi personal $PROMPT_COMMAND, es complicado .
Mark Haferkamp
@ MarkHaferkamp Whoa, tienes toda la razón, mi mal. Actualizará la respuesta ahora.
Camilo Martin
1
@MarkHaferkamp Bash y sus peligros oscuros ...: P
Camilo Martin
19

Si mañana decides que no amas a Marry, ella también puede ser reemplazada:

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

Debe haber 50 formas de dejar a tu amante.

Josh Habdas
fuente
9

Como no puedo agregar un comentario. @ruaka Para que el ejemplo sea más legible, escríbalo así

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
fuente
Hasta que llegué a su ejemplo, había entendido el orden al revés. Esto ayudó a aclarar lo que está sucediendo
raghav710
5

Pura POSIX método de cáscara, que a diferencia de Romano Kazanovskyi 's sedrespuesta basada no necesita herramientas externas, sólo propios nativos de la concha Expansiones de parámetros . Tenga en cuenta que los nombres largos de archivo se minimizan para que el código se ajuste mejor en una línea:

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

Salida:

I love Sara and Marry

Cómo funciona:

  • Eliminar el patrón de sufijo más pequeño. "${f%$t*}"devuelve " I love" si el sufijo $t" Suzi*" está en $f" ". I love Suzi and Marry

  • Pero si t=Zelda, entonces "${f%$t*}"no elimina nada y devuelve toda la cadena " I love Suzi and Marry".

  • Esto se utiliza para probar si $testá dentro $fde lo [ "${f%$t*}" != "$f" ]cual se evaluará como verdadero si la $fcadena contiene " Suzi*" y falso si no.

  • Si la prueba devuelve verdadero , construya la cadena deseada usando Eliminar patrón de sufijo más pequeño ${f%$t*} " I love" y Eliminar patrón de prefijo más pequeño ${f#*$t} " and Marry", con la segunda cadena $s" Sara" en el medio.

agc
fuente
5

Sé que esto es viejo, pero como nadie mencionó el uso de awk:

    firstString="I love Suzi and Marry"
    echo $firstString | awk '{gsub("Suzi","Sara"); print}'
Payam
fuente
1
Ese truco es el camino a seguir si lo que está tratando de dividir no está realmente en una variable sino en un archivo de texto. Gracias, @Payam!
Felipe SS Schneider
2
echo [string] | sed "s/[original]/[target]/g"
  • "s" significa "sustituto"
  • "g" significa "global, todas las ocurrencias coincidentes"
Alberto Salvia Novella
fuente