¿Cómo usar sed para replace un patrón si ocurre en una línea que sigue otro patrón?

Diga, el siguiente es el contenido de un file

$ cat file Name=Tom Value=10 Name=Tom Name=Harry Value=20 

En alguna ocasión, el valor no ocurre después de un nombre. Entonces, lo que necesito hacer es encontrar el patrón Name = Tom y replace el valor, pero solo si ocurre en la línea inmediatamente posterior al nombre. ¿Como hacer eso?

Más compacto y algo más fácil:

 sed 'G;/^Value=/s/=.*\nName=Tom/=42/;s/\n.*//;h' file 

Esto agrega la línea anterior desde el espacio de espera a cada línea. Para Value= lines, el valor se reemplaza si Name=Tom estaba en el espacio de espera. De lo contrario, la línea añadida se elimina y la línea se almacena en el espacio de espera para el próximo ciclo.

con awk :

 awk 'prev ~ /^Name=(Tom|Anything)$/ && /^Value=/ {$0 = "Value=123"} {print; prev = $0}' 

O con el nombre y el nuevo valor almacenados en una variable:

 export NAME=Tom VALUE=123 awk 'prev == "Name=" ENVIRON["NAME"] && /^Value=/ { $0 = "Value=" ENVIRON["VALUE"] } {print; prev = $0}' 

Con sed , el equivalente sería algo así como:

 sed -e 'x;/^Name=Tom$/{g;/^Value/s/=.*/=123/;h;b' -e '}' -eg 

Aunque, como siempre, es más corto pero significativamente less legible que con awk . Además, para trabajar con valores arbitrarios almacenados en variables, sed no tiene el equivalente de ENVIRON de awk , o la capacidad de hacer una comparación simple de cadenas (a diferencia de la comparación de expresiones regulares), por lo que necesitaría preprocesar el valores de las variables primero a less que pueda garantizar que las variables no contengan caracteres especiales para sed .

 sed -e "x;/^Name=$NAME\$/{g;/^Value/s/=.*/=$VALUE/;h;b" -e '}' -eg 

Sería una vulnerabilidad de inyección de command (al less con GNU sed ) si $NAME y $VALUE no están estrechamente controlados.

 sed -e ' /^Name=Tom$/!b $!N /\(\nValue\)=.*/s//\1=NEW_VALUE/ P;D ' data.inp 

Explicación

  • Buscará líneas que tengan el nombre Tom en ellas. No coincidencia irá a stdout.
  • Luego, intentará agregar la siguiente línea al espacio del patrón, siempre que no sea la última línea.
  • Buscará el valor regex Value = en la línea que acaba de adjuntarse y, en caso afirmativo, el valor se cambiará a NEW_VALUE (debe cambiarlo a lo que necesite).
  • P; D => imprimirá la parte del espacio del patrón a la izquierda de \ n, es decir, la línea anterior que tenía Name = Tom y luego elimina esta parte. Ahora nos quedamos con Value = NEW_VALUE y con esto salta al principio de la línea de script sed: / ^ Name = Tom $ /! B Obviamente, esto va a fallar, y por lo tanto será llevado al stdout. Esto se hace para ocuparse de las líneas consecutivas Nombre = Tomar.

Resultados

 Name=Tom Value=NEW_VALUE Name=Tom Name=Harry Value=20