Escapar de una cadena para un patrón de reemplazo de sed


317

En mi script bash tengo una cadena externa (recibida del usuario), que debería usar en el patrón sed.

REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"

¿Cómo puedo escapar de la $REPLACEcadena para que sea aceptada de forma segura sedcomo un reemplazo literal?

NOTA: El KEYWORDes una subcadena mudo de coincidencias, etc. No es suministrada por el usuario.


13
¿Está tratando de evitar el problema de "Mesitas pequeñas" si dicen "/ g -e 's / PASSWORD =. * / PASSWORD = abc / g'"?
Paul Tomblin el

2
Si usa bash, no necesita sed. Solo useoutputvar="${inputvar//"$txt2replace"/"$txt2replacewith"}".
destenson

@destenson Creo que no deberías poner las dos variables fuera de las comillas. Bash puede leer variables dentro de comillas dobles (en su ejemplo, el espacio en blanco podría arruinar las cosas).
Camilo Martin


1
@CamiloMartin, mira mi comentario en mi propia respuesta. Las comillas dentro de $ {} no coinciden con las comillas dentro. Las dos variables no están fuera de las comillas.
destenson

Respuestas:


268

Advertencia : Esto no considera nuevas líneas. Para una respuesta más profunda, vea esta pregunta SO en su lugar. (Gracias, Ed Morton y Niklas Peter)

Tenga en cuenta que escapar de todo es una mala idea. Sed necesita escapar de muchos personajes para obtener su significado especial. Por ejemplo, si escapa un dígito en la cadena de reemplazo, se convertirá en una referencia inversa.

Como dijo Ben Blank, solo hay tres caracteres que deben escaparse en la cadena de reemplazo (se escapa, barra diagonal para el final de la declaración y & para reemplazar todos):

ESCAPED_REPLACE=$(printf '%s\n' "$REPLACE" | sed -e 's/[\/&]/\\&/g')
# Now you can use ESCAPED_REPLACE in the original sed statement
sed "s/KEYWORD/$ESCAPED_REPLACE/g"

Si alguna vez necesita escapar de la KEYWORDcadena, la siguiente es la que necesita:

sed -e 's/[]\/$*.^[]/\\&/g'

Y puede ser utilizado por:

KEYWORD="The Keyword You Need";
ESCAPED_KEYWORD=$(printf '%s\n' "$KEYWORD" | sed -e 's/[]\/$*.^[]/\\&/g');

# Now you can use it inside the original sed statement to replace text
sed "s/$ESCAPED_KEYWORD/$ESCAPED_REPLACE/g"

Recuerde, si usa un carácter que no sea /delimitador, debe reemplazar la barra diagonal en las expresiones anteriores con el carácter que está usando. Vea el comentario de PeterJCLaw para una explicación.

Editado: debido a algunos casos de esquina que anteriormente no se tenían en cuenta, los comandos anteriores han cambiado varias veces. Verifique el historial de edición para más detalles.


17
Vale la pena señalar que puede evitar tener que escapar de las barras diagonales al no usarlas como delimitadores. La mayoría de las versiones (¿todas?) De sed le permiten usar cualquier carácter, siempre que se ajuste al patrón: $ echo 'foo / bar' | sed s _ / _: _ # foo: bar
PeterJCLaw

2
sed -e 's / (\ / \ | \\\ | &) / \\ & / g' no funcionó para mí en OSX pero esto sí funciona: sed 's / ([\\\ / &]) / \\ & / g 'y es un poco más corto.
jcoffland el

1
Para el patrón de búsqueda KEYWORD, en GNU sed , aquí hay 2 caracteres más ^, $no mencionados anteriormente:s/[]\/$*.^|[]/\\&/g
Peter.O

1
@Jesse: Corregido. De hecho, ese es el error que advierto en el primer párrafo. Supongo que no practico lo que predico.
Pianosaurus

1
@NeronLeVelu: No estoy seguro de saber a qué te refieres, pero "no tiene un significado especial en tuberías o variables. El shell lo analiza antes de ejecutar el resultado, por lo que las comillas dobles dentro de las variables son seguras. Por ejemplo, intenta ejecutar A='foo"bar' echo $A | sed s/$A/baz/en bash. Las comillas dobles se tratan como el 'foo' y 'bar' a su alrededor.
Pianosaurus

92

El comando sed le permite usar otros caracteres en lugar de /como separador:

sed 's#"http://www\.fubar\.com"#URL_FUBAR#g'

Las comillas dobles no son un problema.


55
Todavía necesita escapar, .que de lo contrario tiene un significado especial. Edité tu respuesta.
ypid

Acabo de intentar hacer: sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' filecon sed '|CLIENTSCRIPT="foo"|a CLIENTSCRIPT2="hello"' filey eso no hace lo mismo.
Dimitri Kopriwa

1
Debido a que esto solo se aplica al sustituto, esto debería decir: El scomando (como sustituto) de sed le permite usar otros caracteres en lugar de / como separador. Además, esta sería una respuesta a cómo usar sed en URL con caracteres de barra diagonal. No responde a la pregunta de OP cómo escapar de una cadena ingresada por un usuario, que podría contener /, \, pero también # si decide usar eso. Y además, URI puede contener # también
papo

2
¡cambio mi vida! ¡Gracias!
Franciscon Santos

48

Los únicos tres caracteres literales que se tratan especialmente en la cláusula de reemplazo son /(para cerrar la cláusula), \(para escapar de caracteres, referencia, etc.) y &(para incluir la coincidencia en el reemplazo). Por lo tanto, todo lo que necesitas hacer es escapar de esos tres personajes:

sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"

Ejemplo:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar

También una nueva línea, creo. ¿Cómo escapo de una nueva línea?
Alexander Gladysh

2
Tenga cuidado con el comportamiento predeterminado de echo con respecto a las barras invertidas. En bash, echo tiene como valor predeterminado la no interpretación de los escapes de barra invertida, lo que sirve para el propósito aquí. En guión (sh), por otro lado, echo interpreta los escapes de barra invertida y no tiene forma, hasta donde yo sé, de suprimir esto. Por lo tanto, en el guión (sh), en lugar de echo $ x, haga printf '% s \ n' $ x.
Youssef Eldakar

Además, utilice siempre la opción -r cuando realice una lectura para tratar las barras diagonales inversas en la entrada del usuario como literales.
Youssef Eldakar

Para la compatibilidad multiplataforma con otros shells, debe consultar este documento sobre el reemplazo de caracteres especiales sed: grymoire.com/Unix/Sed.html#toc-uh-62
Dejay Clayton

2
@Drux Los tres caracteres son los únicos especiales en la cláusula de reemplazo . Mucho más es especial en la cláusula del patrón.
lenz

33

Basado en las expresiones regulares de Pianosaurus, hice una función bash que escapa tanto a la palabra clave como al reemplazo.

function sedeasy {
  sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

Así es como lo usa:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf

3
¡Gracias! si alguien más recibe un error de sintaxis al intentar usarlo, al igual que yo, solo recuerde ejecutarlo con bash, no sh
Konstantin Pereiaslov

1
¿Existe una función solo para escapar de una cadena para sed en lugar de envolverse alrededor de sed?
CMCDragonkai

Oye, solo una advertencia general con respecto a iniciar tuberías con un eco como este: algunas (¿la mayoría?) Implementaciones de opciones de eco (ver man echo), haciendo que la tubería se comporte inesperadamente cuando su argumento $1comienza con un guión. En cambio, puede comenzar su tubería con printf '%s\n' "$1".
Pianosaurus

17

Es un poco tarde para responder ... pero hay una manera mucho más simple de hacer esto. Simplemente cambie el delimitador (es decir, el carácter que separa los campos). Entonces, en lugar de s/foo/bar/escribir s|bar|foo.

Y, aquí está la manera fácil de hacer esto:

sed 's|/\*!50017 DEFINER=`snafu`@`localhost`\*/||g'

El resultado resultante carece de esa desagradable cláusula DEFINER.


10
No, &y `` aún se debe escapar, al igual que el delimitador, lo que se elija.
mirabilos

3
Eso resolvió mi problema, ya que tenía caracteres "/" en una cadena de reemplazo. ¡Gracias hombre!
Evgeny Goldin

funciona para mi. Lo que estoy haciendo es tratar de escapar $en la cadena a punto de cambiar, y mantener el significado de $en la cadena de reemplazo. Digamos que quiero cambiar $XXXel valor de variable $YYY, sed -i "s|\$XXX|$YYY|g" filefunciona bien.
hakunami

11

Resulta que estás haciendo la pregunta equivocada. También hice la pregunta equivocada. La razón por la que está mal es el comienzo de la primera oración: "En mi script bash ...".

Tuve la misma pregunta y cometí el mismo error. Si usa bash, no necesita usar sed para hacer reemplazos de cadenas (y es mucho más limpio usar la función de reemplazo incorporada en bash).

En lugar de algo como, por ejemplo:

function escape-all-funny-characters() { UNKNOWN_CODE_THAT_ANSWERS_THE_QUESTION_YOU_ASKED; }
INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A="$(escape-all-funny-characters 'KEYWORD')"
B="$(escape-all-funny-characters '<funny characters here>')"
OUTPUT="$(sed "s/$A/$B/g" <<<"$INPUT")"

puedes usar las funciones de bash exclusivamente:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"

Por cierto, el resaltado de sintaxis aquí está mal. Las comillas exteriores coinciden y las comillas interiores coinciden. En otras palabras, parece $Ay $Bno están entre comillas, pero no lo son. Las comillas dentro de la ${}no coinciden con las comillas fuera de ella.
destenson

En realidad, no tiene que citar el lado derecho de una tarea (a menos que quiera hacer algo como var='has space'): OUTPUT=${INPUT//"$A"/"$B"}es seguro.
Benjamin W.

En realidad, no tiene que citar el lado derecho de una tarea (a menos que quiera que funcione en el mundo real y no solo como un guión de juguete para mostrar su habilidad). Siempre trato de citar cada expansión de variables que no quiero que interprete el shell, a menos que tenga una razón específica para no hacerlo. De esa manera, las cosas tienden a romperse con menos frecuencia, especialmente cuando se les proporciona información nueva o inesperada.
destenson

1
Consulte el manual : "Todos los valores se someten a expansión de tilde, expansión de parámetros y variables, sustitución de comandos, expansión aritmética y eliminación de comillas (detallados a continuación)". Es decir, lo mismo que entre comillas dobles.
Benjamin W.

1
¿Qué pasa si necesita usar sed en un archivo?
Efren

1

Use awk, es más limpio:

$ awk -v R='//addr:\\file' '{ sub("THIS", R, $0); print $0 }' <<< "http://file:\_THIS_/path/to/a/file\\is\\\a\\ nightmare"
http://file:\_//addr:\file_/path/to/a/file\\is\\\a\\ nightmare

2
El problema awkes que no tiene nada similar sed -i, lo que resulta extremadamente útil el 99% del tiempo.
Tino

Este es un paso en la dirección correcta, pero awk todavía interpreta algunos metacaracteres en su sustitución, por lo que aún no es seguro para la entrada del usuario.
Jeremy Huiskamp

0

Aquí hay un ejemplo de un AWK que usé hace un tiempo. Es un AWK que imprime nuevos AWKS. AWK y SED son similares, puede ser una buena plantilla.

ls | awk '{ print "awk " "'"'"'"  " {print $1,$2,$3} " "'"'"'"  " " $1 ".old_ext > " $1 ".new_ext"  }' > for_the_birds

Parece excesivo, pero de alguna manera esa combinación de citas funciona para mantener el 'impreso como literales. Entonces, si recuerdo correctamente, las variables están rodeadas de comillas como esta: "$ 1". Pruébalo, avísame cómo funciona con SED.


0

Tengo una mejora sobre la función sedentaria, que romperá con caracteres especiales como tabulación.

function sedeasy_improved {
    sed -i "s/$(
        echo "$1" | sed -e 's/\([[\/.*]\|\]\)/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/$(
        echo "$2" | sed -e 's/[\/&]/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/g" "$3"
}

Entonces, ¿qué es diferente? $1y $2entre comillas para evitar expansiones de shell y preservar pestañas o espacios dobles.

Tubería adicional | sed -e 's:\t:\\t:g'(me gusta :como token) que transforma una pestaña en \t.


Pero vea mi comentario sobre la respuesta sedentaria con respecto al uso del eco en las tuberías.
Pianosaurus

0

Estos son los códigos de escape que he encontrado:

* = \x2a
( = \x28
) = \x29

" = \x22
/ = \x2f
\ = \x5c

' = \x27
? = \x3f
% = \x25
^ = \x5e

-1

no olvides todo el placer que se produce con la limitación de shell "y"

entonces (en ksh)

Var=">New version of \"content' here <"
printf "%s" "${Var}" | sed "s/[&\/\\\\*\\"']/\\&/g' | read -r EscVar

echo "Here is your \"text\" to change" | sed "s/text/${EscVar}/g"

exactamente la dirección que necesitaba, para escapar de los resultados de búsqueda, encontrado a través de Google, por lo que puede ser útil para alguien - terminó con - sed "s / [& \\\ * \\" \ '\ "') (] / \\ & / g '
MolbOrg

-1

Si solo está buscando reemplazar el valor de la Variable en el comando sed, simplemente elimine el Ejemplo:

sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test

-2

Si el caso es que está generando una contraseña aleatoria para pasar para sedreemplazar el patrón, entonces elige tener cuidado con qué conjunto de caracteres en la cadena aleatoria. Si elige una contraseña creada al codificar un valor como base64, entonces solo hay un carácter que es posible en base64 y también es un carácter especial en el sedpatrón de reemplazo. Ese carácter es "/", y se elimina fácilmente de la contraseña que está generando:

# password 32 characters log, minus any copies of the "/" character.
pass=`openssl rand -base64 32 | sed -e 's/\///g'`;

-4

Una manera más fácil de hacer esto es simplemente construir la cadena de antemano y usarla como parámetro para sed

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt

Falla y extremadamente peligroso, ya que REPLACE es suministrado por el usuario: REPLACE=/dased: -e expression #1, char 12: unknown option to `s'
Tino
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.