¿Cómo puedo usar variables en el LHS y RHS de una sustitución sed?

61

Quiero hacer:

cat update_via_sed.sh | sed 's/old_name/new_name/' > new_update_via_sed.sh

en mi programa

Pero quiero usar variables, por ejemplo

old_run='old_name_952'
new_run='old_name_953'

He intentado usarlos pero la sustitución no ocurre (sin error). Yo he tratado:

cat update_via_sed.sh | sed 's/old_run/new_run/'
cat update_via_sed.sh | sed 's/$old_run/$new_run/'
cat update_via_sed.sh | sed 's/${old_run}/${new_run}/'
Michael Durrant
fuente
2
Puede encontrar la respuesta en Usar un parámetro en un argumento de comando .
manatwork

Respuestas:

45

Podrías hacerlo:

sed "s/$old_run/$new_run/" < infile > outfile

Pero tenga en cuenta que $old_runse tomaría como una expresión regular y, por lo tanto, cualquier carácter especial que contenga la variable, como /o .tendría que ser escapado . Del mismo modo, en $new_run, los caracteres &y \tendrían que ser tratados especialmente y tendrías que escapar de los /caracteres de nueva línea.

Si el contenido de $old_runo $new_runno está bajo su control, entonces es crítico realizar ese escape, o de lo contrario ese código equivale a una vulnerabilidad de inyección de código .

Stéphane Chazelas
fuente
3
Estaba usando comillas simples en parámetros de sed, cambié a comillas dobles y funcionó el archivo. Increíble.
Aakash
no es que sedsolo no usará regex, ¿pero lo sed -Eharía?
Kiwy
@Kiwy, no. -ees especificar una expresión sed e , para que pueda pasar más de uno (como en sed -e exp1 -e exp2) o combinaciones de archivos y expresiones ( sed -f file -e exp). Puede ser confuso -Eque con algunas implementaciones, le dice a sed que use ERE en lugar de BRE.
Stéphane Chazelas
Escribí mal, pero está bien, eso explica por qué tengo tantos problemas al usar sed sin -Eque solo domine la expresión regular extendida, no la regular. Gracias por la explicación.
Kiwy
@Kiwy, vea también las preguntas y respuestas vinculadas para obtener más opciones compatibles con algunas sedimplementaciones para sintaxis de expresiones regulares aún más.
Stéphane Chazelas
31

Esto funcionó:

cat update_via_sed.sh | sed 's/'"$old_run"'/'"$new_run"'/'

Como quiero 'reutilizar' el mismo archivo, en realidad lo uso para cualquiera que desee un enfoque similar:

cat update_via_sed.sh | sed 's/'"$old_run"'/'"$new_run"'/' > new_update; mv -f new_update update_via_sed.sh

Lo anterior creó un nuevo archivo y luego elimina el archivo actual que cambia el nombre del nuevo archivo para que sea el archivo actual.

Michael Durrant
fuente
44
No necesita cat, mv o extra '' a menos que tenga caracteres especiales en ellos. Entonces es sed -i s/"$old"/"$new"/ update_via_sed.sh. Sin embargo, tenga en cuenta que esto no funciona para ningún valor de lo antiguo y lo nuevo. por ejemplo, no debe contener / etc., ya que $ old sigue siendo una búsqueda y $ new un patrón de reemplazo donde algunos caracteres tienen significados especiales.
frostschutz
1
sed -i "s/$old/$new/"está bien también (con las precauciones anteriores). sedpor lo general, considera que el separador es el primer carácter después del scomando, es decir, si sus variables contienen barras diagonales, /pero no digamos en ( @), puede usar "s @ $ old @ $ new @".
Peter
22

Puede usar variables en sed siempre que esté entre comillas dobles (no entre comillas simples):

sed "s/$var/r_str/g" file_name >new_file

Si tiene una barra diagonal ( /) en la variable, use un separador diferente como se muestra a continuación

sed "s|$var|r_str|g" file_name >new_file
mani
fuente
1
+1 por mencionar la necesidad de usar comillas dobles. Ese fue mi problema.
speckledcarp
1
Exactamente lo que estaba buscando, incluido el uso de, |como en mi caso, las variables contienen una ruta por lo que tiene /en él
yorch
Por alguna razón, la tubería |funcionó para mí en MacOS 10.14, pero a otros separadores les gusta @o #no. También tenía cortes en mi env var.
davidenke
21

'in-place' sed (usando la bandera -i) fue la respuesta. Gracias a Peter.

sed -i "s@$old@$new@" file
Michael Durrant
fuente
2
Tienes las citas en el lugar equivocado. las comillas deben estar alrededor de variables, no s@(que no es especial para el shell de ninguna manera). Lo mejor es poner todo dentro de comillas como sed -i "s@$old@$new@" file. Tenga en cuenta que -ino es una sedopción estándar .
Stéphane Chazelas
¿Cómo puedo escapar $en este comando? Como, voy a cambiar $xxxen su conjunto al valor de una variable $JAVA_HOME. Lo intenté sed -i "s@\$xxx@$JAVA_HOME@" file, pero el error diceno previous regular expression
hakunami el
3

man bash da esto sobre citas simples

El encerrar caracteres entre comillas simples conserva el valor literal de cada carácter dentro de las comillas. Una comilla simple no puede aparecer entre comillas simples, incluso cuando está precedida por una barra diagonal inversa.

Independientemente de lo que escriba en la línea de comando, bash lo interpreta y luego envía el resultado al programa al que se supone que debe enviarse. En este caso, si usa sed 's/$old_run/$new_run/', bash primero ve el sed, lo reconoce como un presente ejecutable en $PATHvariable . El sedejecutable requiere una entrada. Bash busca la entrada y encuentra 's/$old_run/$new_run/'. Las comillas simples dicen bash para no interpretar el contenido en ellas y pasarlas como están. Entonces, bash luego los pasa a sed. Sed da un error porque $solo puede ocurrir al final de la línea.

En cambio, si usamos comillas dobles, es decir, "s/$old_run/$new_run/"bash lo ve e interpreta $old_runcomo un nombre de variable y realiza una sustitución (esta fase se llama expansión de variable). Esto es de hecho lo que requerimos.

Pero, debes tener cuidado al usar comillas dobles porque, primero se interpretan con bash y luego se dan a sed. Por lo tanto, algunos símbolos como `deben escaparse antes de usarlos.

nitishch
fuente
1

En riesgo de ser impertinente, ¿lo ha considerado perl?

Lo cual, entre otras cosas, es bastante bueno para realizar operaciones de 'estilo sed', pero también es un programa de programación más completo.

Se ve como en su ejemplo, lo que está realmente tratando de hacer es incrementar un número.

Entonces, ¿qué tal:

#!/usr/bin/env perl
use strict;
use warnings 'all'; 

while ( <DATA> ) {
    s/old_name_(\d+)/"old_name_".($1+1)/e;
    print;
}

__DATA__
old_name_932

o como una línea - estilo sed:

perl -pe 's/old_name_(\d+)/"old_name_".($1+1)/e'
Sobrique
fuente
1
$ echo $d1 | sed -i 's/zkserver1/'${d1}'/g' deva_file
sed: -e expression #1, char 26: unterminated `s' command 

En $d1estoy almacenando valores

No puedo reemplazar el valor de la variable, así que probé el siguiente comando que funciona

echo $d1 | sed -i 's/zkserver1/'"${d1}"'/g' deva_file

faltan comillas dobles "${d1}", ese fue el error

deva
fuente
-2

Asumido $old_runy $new_runya está configurado:

cat update_via_sed.sh | eval $(print "sed 's/$old_run/$new_run/g'")

Esto muestra el cambio en la pantalla. Puede redirigir la salida al archivo si es necesario.

Orlando
fuente
-2

Para usar una variable en sed, use comillas dobles "", para encerrar la variable, en el patrón que está usando. Por ejemplo: a = "Hola" Si desea agregar la variable 'a' a un patrón, puede usar lo siguiente: sed -n "1, / patrón" $ {a} "patrón / p" nombre de archivo

Sher
fuente
2
La expansión de $ano se cita aquí, lo que significa que cualquier espacio en su valor invocará la división de palabras en el shell.
Kusalananda
-2

También puedes usar Perl:

perl -pi -e ""s/$val1/$val2/g"" file
Samba
fuente
Considere las variables que pueden tener espacios en sus valores. Las cadenas vacías alrededor de la expresión Perl ( "") no harán nada.
Kusalananda
-2

romper la cuerda

malo => Linux ve una cadena $ old_run:

sed 's/$old_run/$new_run/'

bueno => Linux ve una variable $ old_run:

sed 's/'$old_run'/'$new_run'/'
marcdahan
fuente
1
¿Y si $old_runo $new_runcontiene espacios?
Kusalananda