Diferencia entre revisiones de «Sed»

De Jose Castillo Aliaga
Ir a la navegación Ir a la búsqueda
 
(No se muestran 4 ediciones intermedias del mismo usuario)
Línea 23: Línea 23:
El editor sed cambia exactamente lo que se dice que sustituya. Así que si se ejecuta:
El editor sed cambia exactamente lo que se dice que sustituya. Así que si se ejecuta:


     eco sunday | sed 's /day/night/'
     eco sunday | sed 's/day/night/'


La salida sería "Sunnight" porque sed encuentra la cadena de "day" en la entrada.
La salida sería "Sunnight" porque sed encuentra la cadena de "day" en la entrada.
Línea 61: Línea 61:




Este ejemplo tiene una peculiaridad. Puesto que le decimos que susituya un número formado por 0 a muchos números de 0 a 9, aunque no haya números, pondrá el espacio que hay entre los &. Si queremos asegurarnos de que sólo lo hace con números podemos poner:
Este ejemplo tiene una peculiaridad. Puesto que le decimos que sustituya un número formado por 0 a muchos números de 0 a 9, aunque no haya números, pondrá el espacio que hay entre los &. Si queremos asegurarnos de que sólo lo hace con números podemos poner:


     echo "123 456" | sed 's/[0-9][0-9]*/& &/'  
     echo "123 456" | sed 's/[0-9][0-9]*/& &/'  
Línea 90: Línea 90:
* Una posible mejora incluiría el + en vez del * para asegurarse de que se cambian dos palabras con al menos una letra. Recuerda que el + ha de ser \+ para que no lo incluya en la cadena a buscar.
* Una posible mejora incluiría el + en vez del * para asegurarse de que se cambian dos palabras con al menos una letra. Recuerda que el + ha de ser \+ para que no lo incluya en la cadena a buscar.


Pero el \1 no sólo puede estar en el patrón de sustitución, puede estar en el de búsqueda. Por ejemplo, para borrar palabras duplicadas:
Pero el \1 no sólo puede estar en el patrón de sustitución, puede estar en el de búsqueda. Por ejemplo, para borrar palabras duplicadas en minúsculas:


     sed 's/\([a-z]*\) \1/\1/'
     sed 's/\([a-z]*\) \1/\1/'
Línea 101: Línea 101:


     sed 's/^\(.\)\(.\)\(.\)/\3\2\1/'
     sed 's/^\(.\)\(.\)\(.\)/\3\2\1/'
echo "Ignacio Paches giner" | sed 's/\([a-Z]\+\) \([a-Z]\+\) \([a-Z]\+\)/ \2 \3 ,\1/'
echo "Ig d " | sed 's/\([a-Z]*\) \([a-Z]*\) \([a-Z]*\)/ \2 \3 ,\1/'
# ¿Qué diferencia tienen estos dos comandos?


=== Etiquetas para el s de sed ===
=== Etiquetas para el s de sed ===
Línea 108: Línea 113:


Si al final de la expresión de s ponemos un /g, le decimos que lo haga para todas las ocurrencias de ese patrón. Si no lo ponemos sólo lo hará en la primera.
Si al final de la expresión de s ponemos un /g, le decimos que lo haga para todas las ocurrencias de ese patrón. Si no lo ponemos sólo lo hará en la primera.
$ echo "aabbaaccaadd" | sed 's/aa/AA/'
AAbbaaccaadd


'''/1 /2 etc.. Para reemplazar la ocurrencia n.'''
'''/1 /2 etc.. Para reemplazar la ocurrencia n.'''
Línea 132: Línea 141:
'''/p Imprimir la línea que ha sido modificada'''
'''/p Imprimir la línea que ha sido modificada'''


Si usamos la opción -n, no imprime nada a la salida a menos que le pongamos /p para que la imprima. Esto une la funcionalidad de grep a la de sed.
Con la opción -n (puesta antes del patrón) desactivamos la impresión por defecto de cualquier línea. Usado con los comandos anteriores, no imprimirá nada. Pero, a continuación,  se puede combinar con el comando p (después del patrón) para imprimir por salida estándar las líneas que han cumplido con el patrón. Esto nos convierte a sed en un sustituto de grep.


Se puede imitar totalmente el grep con sed:
Se puede imitar totalmente el grep con sed:
Línea 207: Línea 216:
     sed '/^g/s/g/s/g'
     sed '/^g/s/g/s/g'


Resulta oscuro y confuso, ya la última g es para indicar que se haga en todas las ocurrencias, la primera es para hacerlo en las líneas que empiezan por g y el del medio para buscar la g. Lo mismo pasa con la s, que la primera significa sustituir y la otra es el patrón de sustitución. Si realmente se intenta hacer lo que hace, se puede hacer más legible con un espacio y usando _ en vez de /:
Resulta oscuro y confuso, ya que la última g es para indicar que se haga en todas las ocurrencias, la primera es para hacerlo en las líneas que empiezan por g y el del medio para buscar la g. Lo mismo pasa con la s, que la primera significa sustituir y la otra es el patrón de sustitución. Si realmente se intenta hacer lo que hace, se puede hacer más legible con un espacio y usando _ en vez de /:


     sed '/^g/ s_g_s_g'
     sed '/^g/ s_g_s_g'
Línea 613: Línea 622:


http://es.kioskea.net/faq/3063-sed-introduccion-a-sed-parte-i
http://es.kioskea.net/faq/3063-sed-introduccion-a-sed-parte-i
http://diariolinux.com/2013/08/11/hackit-2013-level-6-expresiones-regulares-y-sed-ii/
https://github.com/themattrix/sed2048#

Revisión actual - 10:02 25 mar 2014

La potencia de las herramientas tradicionales de la terminal de Linux nos permite hacer grandes cosas sólo enlazando comandos y tuberías. Herramientas como tr, cut, tail y, sobretodo, grep, facilitan el trabajo al administrador de sistemas o al usuario normal.

Pero para la manipulación de textos, cuando esta implica modificar palabras completas, sustituir frases o extraer fragmentos concretos, se necesita una herramienta como sed o awk. Estas utilidades tienen su própio lenguaje para ejecutar tareas complejas.

Sed es un editor de lineas que funciona como un filtro: cat | sed | tail... Todas las salidas van a stdout a menos que se indique lo contrario.

A continuación se explicará sed con ejemplos y una cierta profundidad. Las posibilidades son tantas que es mejor ver ejemplos y extrapolar su utilidad para las necesidades concretas.

Además de la teoría, hay una sección de ejercicios de sed

Sed para sustituir

Sed tiene muchas opciones, pero la mayoría de la gente sólo aprende la de sustituir. Es muy útil para cambiar una cadena de caracteres por otra. Esta cadena de caracteres puede ser una concreta o una expresión regular.

Veamos este sencillo ejemplo:

   sed s/day/night/ <old >new
   # Sustituye en el fichero old, la primera ocurrencia de cada línea de la cadena “day” por night.
  • Yo no he puesto entre comillas, porque el argumento de este ejemplo no las necesitaba. Sin embargo, se recomienda usar comillas. Si hay meta-caracteres en el comando, las comillas son necesarias. Es un buen hábito, por lo que lo seguiremos de ahora en adelante.
   sed ‘s/day/night/’ <old >new 

El editor sed cambia exactamente lo que se dice que sustituya. Así que si se ejecuta:

   eco sunday | sed 's/day/night/'

La salida sería "Sunnight" porque sed encuentra la cadena de "day" en la entrada.

Hay cuatro partes en este comando de sustitución:

  • s comando Sustituto
  • / .. / .. / Delimitador
  • day patrones de expresiones regulares de búsqueda de patrones
  • night cadena de reemplazo


El delimitador por defecto es el slash (/), pero si nos encontramos con este ejemplo:

   sed 's/\/usr\/local\/bin/\/common\/bin/' <old >new 

Puesto que la cadena a sustituir tiene / en ella, se necesita usar un delimitador \ para que la interprete como un caracter normal. Esto puede resultar incómodo, por lo que se puede usar otro caracter:

   sed 's_/usr/local/bin_/common/bin_' <old >new 
   sed 's:/usr/local/bin:/common/bin:' <old >new 
   sed 's|/usr/local/bin|/common/bin|' <old >new 


La utilidad de & para la sustitución.

Además de las expresiones regulares, sed incorpora el & para referirse a la cadena concordante con la expresión regular anterior. Esto sirve para modificar los alrededores de la cadena.

Por ejemplo, si queremos poner paréntesis:

   sed 's/abc/(abc)/' <old >new 

Esto sólo funciona si sabes lo que buscas, pero si la cadena puede ser, por ejemplo, una combinación de a,b y c, pero sin saber cual, necesitas el & para referirte a la anterior.

   sed 's/[a-c]*/(&)/' <old >new

Se puede poner la cantidad de & que sean necesarias en la cadena de sustitución:

   echo "123 456" | sed 's/[0-9]*/& &/'    
   123 123 456


Este ejemplo tiene una peculiaridad. Puesto que le decimos que sustituya un número formado por 0 a muchos números de 0 a 9, aunque no haya números, pondrá el espacio que hay entre los &. Si queremos asegurarnos de que sólo lo hace con números podemos poner:

   echo "123 456" | sed 's/[0-9][0-9]*/& &/' 

o

   echo "123 456" | sed 's/[0-9]\+/& &/'  
   # (el + no lo soportaba el sed original, el GNU sí)


Usando \1 para mantener una parte del patrón

Sed tiene la capacidad de recordar hasta 9 patrones para extraerlos de un patrón más general. Por ejemplo, si queremos quedarnos sólo con la primera palabra de una línea y descartar todo lo demás, podemos marcarla entre paréntesis. Los paréntesis han de estar detrás de \ para que no los entienda como parte de la cadena a buscar.

   sed 's/\([a-z]*\).*/\1/'

Para ver la utilidad, prueba e intenta entender cómo funciona esto:

   echo abcd123 | sed 's/\([a-z]*\).*/\1/'

Si quieres dar la vuelta a dos palabras, se puede hacer con sed de la siguiente manera:

   sed 's/\([a-z]*\) \([a-z]*\)/\2 \1/' 

Prueba:

   echo "hola mundo" | sed 's/\([a-z]*\) \([a-z]*\)/\2 \1/'
  • Una posible mejora incluiría el + en vez del * para asegurarse de que se cambian dos palabras con al menos una letra. Recuerda que el + ha de ser \+ para que no lo incluya en la cadena a buscar.

Pero el \1 no sólo puede estar en el patrón de sustitución, puede estar en el de búsqueda. Por ejemplo, para borrar palabras duplicadas en minúsculas:

   sed 's/\([a-z]*\) \1/\1/'

O para detectarlas:

   sed -n '/\([a-z][a-z]*\) \1/p'

Otro ejemplo. Para dar la vuelta a los tres primeros caracteres de una línea:

   sed 's/^\(.\)\(.\)\(.\)/\3\2\1/'
echo "Ignacio Paches giner" | sed 's/\([a-Z]\+\) \([a-Z]\+\) \([a-Z]\+\)/ \2 \3 ,\1/'
echo "Ig d " | sed 's/\([a-Z]*\) \([a-Z]*\) \([a-Z]*\)/ \2 \3 ,\1/'
# ¿Qué diferencia tienen estos dos comandos?


Etiquetas para el s de sed

Al s de susitituir de sed se le pueden poner etiquetas al final para modificar su comportamiento. Vamos a verlas:

/g - Reemplazo global

Si al final de la expresión de s ponemos un /g, le decimos que lo haga para todas las ocurrencias de ese patrón. Si no lo ponemos sólo lo hará en la primera.

$ echo "aabbaaccaadd" | sed 's/aa/AA/'
AAbbaaccaadd


/1 /2 etc.. Para reemplazar la ocurrencia n.

Si queremos reemplazar, por ejemplo, la segunda vez que se cumple un patrón podemos poner /2 al final.

Por ejemplo, modificar el último número de una IP y ponerlo a 0:

   echo 192.168.1.45 | sed 's/[0-9]\+/0/4'


Borrar la segunda palabra:

   sed 's/[a-zA-Z]* //2' <old >new 

Se puede combinar con g. Por ejemplo, para sustituir la segunda palabra y todas las siguientes por BORRADA:

   sed 's/[a-zA-Z]* /BORRADA /2g' <old >new
   # Fíjate que hay un espacio después de la expresión regular y la palabra para que detecte palabras hasta el espacio. 

Para borrar la contraseña del fichero de passwd:

   sed 's/[^:]*//2' </etc/passwd >/etc/password.new

/p Imprimir la línea que ha sido modificada

Con la opción -n (puesta antes del patrón) desactivamos la impresión por defecto de cualquier línea. Usado con los comandos anteriores, no imprimirá nada. Pero, a continuación, se puede combinar con el comando p (después del patrón) para imprimir por salida estándar las líneas que han cumplido con el patrón. Esto nos convierte a sed en un sustituto de grep.

Se puede imitar totalmente el grep con sed:

   sed -n 's/patron/&/p' <file


/w Guardar la salida en un fichero.

Se pueden guardar las líneas modificadas en un fichero. Por ejemplo, guardar las líneas que terminan en espacio:

   sed -n 's/^[0-9]*[02468] /&/w even' <file

Combinar varias sustituciones

Si no se quiere leer el manual, se pueden hacer cosas como estas:

   sed 's/BEGIN/begin/' <old | sed 's/END/end/' >new 

Esto hace dos sed, lo que significa dos procesos. Un ninja del sed nunca lo invoca más de una vez si no es necesario. Para ello, puede usar -e:

   sed -e 's/BEGIN/begin/' -e 's/END/end/' <old >new

Realmente, -e se puede usar en los ejemplos anteriores, pero al ser sólo uno no hace falta.

Se puede pasar un fichero o muchos al sed. Por ejemplo, este elimina las líneas con comentarios # y las vacías con grep y cuenta el resto con wc -l:

   sed 's/^#.*//' f1 f2 f3 | grep -v '^$' | wc -l

A sed se le puede pasar un fichero con un script para él, en caso de que tenga que hacer muchas cosas:

   sed -f sedscript <old >new

sedscript tiene esto:

   # Comentario del sed, esto cambia las minúsculas por mayúsculas
   s/a/A/gs/e/E/gs/i/I/gs/o/O/gs/u/U/g

También se puede hacer con varias líneas si es un script

   #!/bin/bash
   sed 's/a/A/g 
   s/e/E/g 
   s/i/I/g 
   s/o/O/g 
   s/u/U/g' <old >new

Ejercicios

Ejercicios_de_sed#Ejercicios_de_sustituci.C3.B3n

Limitar el sed a ciertas líneas

Sólo se ha visto un comando, y se puede ver lo poderoso que es sed. Sin embargo, todo lo que está haciendo es un grep y un suplente. Es decir, el comando sustituye cada línea por sí misma, sin preocuparse por las líneas cercanas. Lo que sería útil es la capacidad para restringir la operación de ciertas líneas. Algunas de estas restricciones pueden ser útiles:

  • Especificación de una línea por su número.
  • Especificación de un rango de líneas por número.
  • Todas las líneas que contienen un patrón.
  • Todas las líneas desde el principio de un archivo a una expresión regular
  • Todas las líneas de una expresión regular al final del archivo.
  • Todas las líneas entre dos expresiones regulares.

Sed puede hacer todo eso y mucho más. Todos los comandos de sed puede ser procesado por una dirección, el rango o la restricción al igual que los ejemplos anteriores. La restricción o la dirección precede inmediatamente a la orden.

Restringir a un número de línea:

Si, por ejemplo, queremos quitar los primeros números de la línea 3:

   sed '3 s/[0-9][0-9]*//' <file >new 

Patrones:

Se pueden poner entre slash /. Por ejemplo, para eliminiar el primer número de las líneas que empiezan por #

   sed '/^#/ s/[0-9][0-9]*//' 

Observa este comando sed.

   sed '/^g/s/g/s/g'

Resulta oscuro y confuso, ya que la última g es para indicar que se haga en todas las ocurrencias, la primera es para hacerlo en las líneas que empiezan por g y el del medio para buscar la g. Lo mismo pasa con la s, que la primera significa sustituir y la otra es el patrón de sustitución. Si realmente se intenta hacer lo que hace, se puede hacer más legible con un espacio y usando _ en vez de /:

   sed '/^g/ s_g_s_g'

Rangos por el número de línea:

Observa estos ejemplos:

   sed '1,100 s/A/a/'
   # Aplica la sustitución de la 1 a la 100 
   sed '101,532 s/A/a/'
   # Aplica la sustituciñon de la 101 a la 532
   sed '101,$ s/A/a/'
   # La aplica de la 101 hasta el final, el $ significa que es el final del fichero. (Recuerda que, dentro de una expresión regular, significa final de línea)

Rangos por patrones:

En vez de números, se pueden poner dos patrones entre los cuales hacer la sustitución:

   sed '/start/,/stop/ s/#.*//'

Esto quita los comentarios entre la línea que contiene la palabra start y la que contiene la palabra stop.

Es necesario hacer algunas aclaraciones sobre este sed. Si las palabras start y luego stop se repiten más de una vez, sed aplicará la sustitución en todas las repeticiones. Si ocurre start y luego no hay un stop, sed hará las sustituciones hasta el final del fichero. Hará la sustitución en las líneas que contengan start hasta stop, ambas incluidas. Esto quiere decir que si la línea con start está comentada, esta se descomentará aunque el comentario esté antes de la palabra start. Más adelante se verá cómo hacer modificaciones en un itervalo de patrones sin incluir las líneas donde estos se encuentran.

Se pueden combinar números y patrones. Por ejemplo, este sed descomenta desde la primera línea hasta el start.

   sed -e '1,/start/ s/#.*//'

En el siguiente sed, más completo, podemos ver el -e para hacer dos expresiones en un solo sed y lo que hace es descomentar todo menos las líneas entre start y stop.

sed -e '1,/start/ s/#.*//' -e '/stop/,$ s/#.*//'

Usar intervalos para borrar con d

Se puede simular el funcionamiento de head con los intervalos y el parámetro d en el sed:

   sed '101,$ d' <fichero.txt

Muestra las 100 primeras líneas y borra el resto hasta el final. Cabe recordad que realmente no lo borra en el fichero original.

Un ejemplo puede ser borrar el primer pàrrafo de un texto hasta la primera línea en blanco:

   sed '1,/^$/ d' <file 

Observa este ejemplo que simula el funcionamiento del tail. Puesto que primero averigua la longitud del fichero y luego le resta las 10 líneas que deseamos ver.

   #!/bin/bash
   # Imprime las últimas 10 líneas
   # El primer argumento es el nombre del fichero
   lines=`wc -l $1 `
   start=`expr $lines - 10`
   sed "1,$start d" $1

Ya que se ha mencionado el d para borrar, aquí tenemos algunos ejemplos:

   sed '/^#/ d'
   # borra los comentarios 
   sed -e 's/#.*//' -e '/^$/ d'
  # borra los comentarios y las líneas en blanco

Ejercicios

Ejercicios_de_sed#Ejercicios_de_limitar_sed_a_ciertas_l.C3.ADneas

Usar p para duplicar o imprimir la entrada

Si no se le pasa el parámetro -n a sed, el parámetro p permite duplicar la entrada.


   sed ‘p’ 
   # duplica cada línea, es decir, sale una vez porque no está -n y otra por el p.</nowiki>

Con esto, el parámetro -n y los intervalos podemos encontrar otra forma de imprimir las 100 primeras líneas:

   sed -n '1,100 p' <file 


Y otra forma de emular el grep:

   sed -n '/patron/ p'


Usando ! para invertir el sentido de los parámetros

El símbolo ! sirve para negar en las utilitdades de Unix. En el caso de p, se puede usar para evitar la impresión de la línea que concuerde con el patrón.

   sed -n '/patron/ !p'
   # El resultado es similar al -v de grep

El comando q para terminar la ejecución

Esta es otra manera sencilla de mostrar sólo un determinado número de líneas o hasta que se cumpla un patrón.


   sed '11 q' 
   # imprime hasta la línea 11, en la que termina.

El q no acepta un intervalo, ya que parará en el primero que pueda.

El uso de { } para agrupar parámetros

En este punto, el sed se convierte en una especie de lenguaje de programación. Con la capacidad de unir varios modificadores para aplicarlos a un rango o a un patrón le añade una gran potencia al ‘lenguaje sed’

Debido a que cada comando sed debe comenzar en una nueva línea, al añadir { } se deben listar los comandos en líneas separadas. Lo cual le añade un poco de legibilidad al script.

   sed -n ' /begin/,/end/ {
   s/#.*//
   s/[ ^I]*$//
   /^$/ d
   p
   }'

Este script elimina, entre las líneas que contienen las palabras begin y end, los comentarios, las líneas tabuladas vacías y las líneas vacías completamente.


   #!/bin/sh
   sed '/begin/,/end/ !{ 
   s/#.*//
   s/[ ^I]*$//
   /^$/ d 
   p}'

Este otro tiene un ! y hace que se borren las líneas que no estén entre begin y end.

Ejercicios

Ejercicios_de_sed#Ejercicios_de_agrupar_con_.7B_.7D

Añadir o cambiar líneas

Hay tres opciones para añadir líneas o cambiarlas.

Añadir líneas con a\

El comando a\ añade una línea después de la línea que contenga un patrón. La añade tantas veces como se repita ese patrón.

   cat /etc/passwd | sed '\_/bin/bash_ a\Usuario con bash’
   # Indica debajo de la línea qué usuarios tienen bash. Observa cómo he cambiado el / tradicional por _ para que sea más fácil de hacer.

Insertar líneas con i\

El comando i\ hace lo mismo que el a\, pero este la inserta delante de la línea con el patrón.

Cambiar una línea con c\

Si lo que se quiere es sustituir una línea por otra, se puede usar c\

Observemos un ejemplo con todos y con el uso de { }:

   sed '/PALABRA/ {
   i\
   Añade esta línea antes.
   a\
   Añade esta línea después.
   c\
   Cambia la línea por esta.
   }'

Añadir más de una línea:

En muchas ocasiones se necesita añadir más de una línea, se pueden separar por \ así:

   sed '/WORD/ a\
   Add this line\
   This line\
   And this line'

Los intervalos y los comandos a, i, c

No se puede aplicar diréctamente a\ o \i a un intervalo. Pero c\ si:

   sed '/begin/,/end/ c\***BORRADO***'

Una manera de poder es usando el { }, como en este ejemplo:

   sed '1,$ {a\}'

Añade una línea vacía a cada línea.

Ejercicios

Ejercicios_de_sed#Ejercicios_de_a.C3.B1adir_o_cambiar_l.C3.ADneas

Patrones Multilínea

La mayoría de las utilidades de UNIX están orientadas a la línea. Las expresiones regulares están orientados a la línea. La búsqueda de patrones, que cubre más de una línea no es una tarea fácil. Sed lee en una línea de texto, realiza los comandos que pueden modificar la línea, y las salidas de modificación. El bucle principal de una secuencia de comandos sed tiene este aspecto:

  • La siguiente línea se lee desde el archivo de entrada y se busca si dentro de esta se cumplen los patrones.
  • El recuento de línea se incrementa en uno. (La apertura de un nuevo archivo no restablece este número.)
  • Cada comando sed se examina y se ejecuta.
  • Después de que todos los comandos se examinen, el espacio de patrones es la salida a menos que sed tenga la opción "-n".

Pensemos qué pasa cuando ejecutamos esto:

   cat -n /etc/passwd | sed -n -e '1,7 p' -e '5,9 p'

Estamos diciendo que sólo muestre las líneas 1 a 7 y 5 a 9, hay una superposición. Observa lo que pasa e intenta razonar porqué pasa.

Mostrar el número de línea con =

Si ejecutamos esto:

   cat /etc/passwd | sed -n '/bash/='

Muestra sólo las líneas en las qu ese cumple ese patrón.

Por ejemplo, para contar las líneas de un fichero:

   sed -n '$='

Recordar que $ si no está dentro de / / significa el final del fichero, si está dentro de un patrón con / / significa el final de la línea. Este ejemplo, muestra el número de la última línea, es decir, la cantidad de líneas del fichero.

Podemos saber qué líneas están dentro de un rango:

  sed -n '/patron1/,/patron2/='

Transformar con y

Sed puede actuar como si fuera un tr mediante el comando y. Al igual que el comando s cambia palabras enteras, el comando y cambia letra por letra del primer grupo a las del segundo. Se puede, por ejemplo, pasar de minúsculas a mayúsculas:

   sed 'y/abcdefghijklmnñopqrstuvwxyz/ABCDEFGHIJKLMNÑOPQRSTUVWXYZ/' fichero

Trabajando con múltiples líneas

Hay tres nuevos comandos para utilizar varias líneas de patrones: "N", "D" y "P" Voy a explicar su relación con "n", "d" y "p" que son comandos de una sola línea.

El comando n vs N

El comando n se utiliza para quitar las líneas que cumplan un patrón del total de líneas que serán evaluadas en la siguiente orden del sed. Por ejemplo, si queremos mostrar los usuarios que empiezan por a, pero sólo los que no tienen bash, podemos hacerlo así:

sed -n -e '/bash/n' -e '/^a/p' /etc/passwd

Esto primero ha quitado de las líneas a evaluar aquellas que tienen la cadena bash, el segundo imprime las que empiezan por a, pero sólo de las que quedan. Al tener el -n, sólo salen las que el p de la segunda instrucción indica.

Cuando se trabaja con múltiples líneas, el N lo que hace es aplicar el siguiente comando a la línea siguiente. No evita que la línea en la que está sea evaluada después, como hace el n, sino que permite aplicar un cambio sobre la siguiente línea de la línea que cumple un patrón. Vamos a poner un ejemplo práctico que ayude a entenderlo. El comando ifconfig muestra una salida como esta:

   ifconfig
   eth0      Link encap:Ethernet  direcciónHW 48:5b:30:18:76:fb  
         Direc. inet:192.168.0.196  Difus.:192.168.0.255  Másc:255.255.255.0
         Dirección inet6: fe80::4a5b:39ff:fe18:79fb/64 Alcance:Enlace
         ACTIVO DIFUSIÓN FUNCIONANDO MULTICAST  MTU:1500  Métrica:1
         Paquetes RX:183730 errores:0 perdidos:0 overruns:0 frame:0
         Paquetes TX:132133 errores:0 perdidos:0 overruns:0 carrier:0
         colisiones:0 long.colaTX:1000 
         Bytes RX:233430843 (233.4 MB)  TX bytes:15113038 (15.1 MB)
         Interrupción:18 
   eth1      Link encap:Ethernet  direcciónHW 68:5b:35:18:09:fa  
         ACTIVO DIFUSIÓN MULTICAST  MTU:1500  Métrica:1
         Paquetes RX:0 errores:0 perdidos:0 overruns:0 frame:0
         Paquetes TX:0 errores:0 perdidos:0 overruns:0 carrier:0
         colisiones:0 long.colaTX:1000 
         Bytes RX:0 (0.0 B)  TX bytes:0 (0.0 B)
         Interrupción:17 

Como podemos ver, la IP está en la línea de bajo del eth0 o eth1, si queremos mostra las tarjetas que sean eth[0-9] y que tengan una IP del rango 192.168.0, podemos hacer esto:

   ifconfig | sed -n -e '/eth[0-9]/ {
   N
   /192\.168\.0/p
   }'

Es decir, primero encuentra la línea que tiene eth[0-9]. El N lo que hace es evaluar la siguiente línea (ninguna más) y lo siguiente la imprime sólo si tiene ese patrón.

Vamos a pensar un poco. Imaginemos que tenemos un fichero p1 con este contenido:

   uno
   dos
   uno
   tres
   uno
   uno
   dos2
   cuatro

Si ejecutamos este sed:

   cat p1 | sed -n '/uno/ {
   N
   /dos/p
   }'

El resultado será:

   uno
   dos

¿Encuentras algo raro? Lo raro es que también hay otra pareja de líneas que cumplen esta condición, la de uno y dos2 ¿Cómo puede ser que no las muestre?. La razón es que el algoritmo de sed va recorriendo el fichero, cuando llega a un "uno" evalúa la siguiente línea, pero no la tiene en cuenta para la siguiente iteración. Así, evalúa la línea 1 que pone "uno", como cumple la condición, evalúa la siguiente línea, como tiene la cadena "dos", las imprime. Evalúa la línea 3, no la 2. Esta línea tiene la cadena "uno", pero la siguiente no tiene "dos", así que no las imprime. Pasa a la línea 5, esta tiene la cadena "uno", pero la siguiente no tiene "dos", por lo que tampoco las imprime. Y entonces mira la cadena 7, esta no tiene el "uno", tiene "dos2" y, por tanto, no las imprime.

Si modificamos el fichero a:

   uno
   dos
   uno
   tres
   uno
   dos2
   cuatro

Ya funciona bien.


Ahora tenemos este fichero:

   uno
   dos
   uno
   tres
   uno111111
   222222dos2
   cuatro

Lo que queremos es borrar cualquier cosa que haya entre la cadena "uno" y la cadena "dos" de la siguiente línea.

   sed ' /uno/ {
     N
     /\n.*dos/ {
      s/uno.*\n.*dos/uno dos/
     }
   }' 

Como podemos ver, al no tener el -n también muestra las líneas que no tienen "dos", pero de las que lo tienen, borra el salto de línea y los caracteres que hay en medio.

El comando D vs d

El comando d borraba una línea si contenía cierto patrón. El D borra la línea cuya siguiente línea cumpla un patrón. Si lo aplicamos sobre el fichero del ejemplo anterior de la siguiente manera:

   cat p1 | sed  ' /uno/ {
   N
   /dos/D
   }' 

Borra las líneas con "uno" siempre que estén seguidas de una línea con "dos". Vemos que necesita el N para que evalúe la siguiente línea.

P vs p

También tiene sentido hace justo lo contrario, mostrar sólo las líneas que tienen "uno" y que van seguidas de una línea que tiene "dos". Para ello se usa el P:

   cat p1 | sed  -n ' /uno/ {
   N
   /dos/P
   }'

Si usáramos p mostraría también las líneas con "dos"

Otro ejemplo, queremos mezclar las líneas con su siguiente:

   cat p1 | sed  -e '{
   N
   s/\n//
   }'

Insertar saltos de línea en sed

Puede ser interesante poner un salto de línea después de un determinado patrón. Se puede hacer:

   echo -e "xy\nz" | sed 's/x/x\n/'

o poniedo el santo de línea literal:

   echo -e "xy\nz" | sed 's/x/X\
    /'

Se recomienda esta segunda forma.

Almacenamiento en buffer

Hasta ahora hemos trabajado con tres elementos: el flujo de entrada, el de salida y los patrones. Hay otro elemento, el hold buffer o almacenamiento en buffer. Se puede usar para guardar patrones temporalmente y usarlos después.

Intercambio con x

La x intercambia el patrón por lo almacenado en el hold buffer. Por sí solo no hace nada interesante. Si lo ejecutamos tal cual:

    cat /etc/passwd | sed 'x'

El resultado es el mismo que sin el sed, sólo que hay una línea vacía y la última línea desaparece. Veamos porqué: El hold buffer comienza con una línea vacía. Cuando x reemplaza la primera línea, el hold buffer está vacío y pone una línea vacía. Con la segunda línea, el buffer tiene la primera línea y así sucesivamente.

Uno de los usos es recordar líneas anteriores. Por ejemplo, se puede imitar el parámetro -1, -2 ... de grep que permite ver las líneas anteriores y posteriores a la línea que cumple el patrón.


   sed -n '
   '/$1/' ! { # si no se cumple el patrón pasado por argumentos $1
    x # Guardar en el buffer
    d # borrar
   }
   '/$1/' { # se cumple el patrón
    x # coger la última línea
    p # imprimirla
    x # coger esta línea.
    p # imprimirla
    n # coger la siguiente línea
    p # imprimirla
    a\ --- # mostrar --- después por poner un separador
    x # guardar en el buffer para la siguiente.
   }'

Guardar con h o H

El x lo que hace es cambiar el hold buffer por el patrón actual y viceversa. El comando h símplemente guarda el buffer para luego usarlo sin modificar el actual. H lo que hace es ir añadiendo al buffer. Con esto se puede hacer el script anterior de otra manera:


   sed -n '
   '/$1/' ! { # si no se cumple el patrón pasado por argumentos $1
    h # Guardar en el buffer
   }
   '/$1/' { # se cumple el patrón
    H # añadir al buffer (recordemos que ya estaba la anterior que no ha coincidido)
    n # coger la siguiente línea
    H # y añadirla al buffer
    x # cambiar el actual por el hold buffer que ya tiene las 3 lineas
    p # imprimirlas
    a\--- # añadir el separador
   }'


Ejemplos: http://www.thegeekstuff.com/2009/12/unix-sed-tutorial-7-examples-for-sed-hold-and-pattern-buffer-operations/


A partir de aquí es sólo un borrador

http://www.grymoire.com/Unix/Sed.html#uh-29

http://es.kioskea.net/faq/3063-sed-introduccion-a-sed-parte-i


http://diariolinux.com/2013/08/11/hackit-2013-level-6-expresiones-regulares-y-sed-ii/ https://github.com/themattrix/sed2048#