Sed en una-línea (sed one-liners) Parte II

Sed en una-línea, Parte II

Famous Sed One-Liners Explained, Part II

Dado que no hay muy buena documentación en español acerca del comando sed, me decidí a traducir una serie de excelentes artículos creados por Peteris Krumin. Le he pedido permiso expreso a su autor y no tuvo inconvenientes, así que aquí va.

Nota del Traductor (NdT): No será una traducción total, dado que hay mucha información anecdótica y sólo me limitaré a la explicación del comando sed.

Los one-liners de Eric Pement están divididos en varias secciones:

  1. Espaciado de un archivo (explicado en parte I)
  2. Numeración (explicado en parte I)
  3. Conversión y sustitución de texto (explicado en parte I)
  4. Impresión selectiva de ciertas líneas (explicado aquí)
  5. Eliminación selectiva de ciertas líneas (explicado en parte III)
  6. Aplicaciones especiales (explicado en parte III)

He hecho una cheat sheet que sumariza toda la herramienta sed. Sugiero que la imprimas antes de seguir y la tengas junto a ti. Te ayudará a memorizar los comandos más rápidamente.

Impresión selectiva de ciertas líneas

44. Imprimir las 10 primeras líneas de un archivo (emular “head -10″)

sed 10q

Este one-liner restringe el comando ‘q’ a la línea 10. Esto significa que el comando es ejecutado solo cuando sed lee la décima línea. Para el resto de las líneas no hay comando especificado por lo que la acción predeterminada es imprimir la línea tal y cómo está. Este one-liner imprime las líneas 1 a 9 sin modificar y en la décima termina. ¿Notas algo extraño? Se supone que imprimiríamos las 10 primeras líneas de un archivo, pero parece que sólo se imprimen las primeras 9… No te preocupes. El comando ‘q’ es astuto por naturaleza. Al salir usando ‘q’, sed imprime el contenido del pattern space y sólo después de eso termina. Como resultado se imprimen las líneas del 1 al 10.

Por favor ve la primera parte de este artículo para una explicación de ‘pattern space‘.

45. Imprimir la primera línea de un archivo (emular “head -1″).

sed q

La explicación es prácticamente la misma que para el ejemplo anterior. Sed termina e imprime el contenido del pattern space (que es justamente la primera línea).

46. Imprimir las últimas 10 líneas de un archivo (emular “tail -10″).

sed -e :a -e '$q;N;11,$D;ba'

Este sed es complejo de explicar. Siempre mantiene las últimas 10 líneas en el pattern space y en la última línea sed termina y las imprime.

La primer comando ‘-e :a’ crea una etiqueta llamada ‘a’. El segundo ‘-e’ hace lo siguiente ‘$q’ : si es la última línea, sale y se imprime el pattern space, si no, ejecuta tres comandos ‘N’, ’11,$D’ y ‘ba’. El comando ‘N’ lee la línea siguiente de la entrada y la agrega al pattern space. La línea es separada del resto del pattern space por un caracter de nueva línea. El comando ’11,$D’ ejecuta el comando ‘D’ si el número de la línea actual es nayor o igual a 11 (’11,$’ indica desde la línea 11 hasta el final del archivo). El comando ‘D’ elimina la porción del pattern space hasta el primer caracter de nueva línea. El último comando ‘ba’ vuelve a la etiqueta llamada ‘a’ (creada al principio). Esto garantiza que el pattern space nunca contiene más de 10 líneas, porque cuando la línea 11 es agregada, la línea 1 es eliminada, cuando la 12 se agrega, se borra la 2, etc.

47. Imprime las últimas 2 líneas de un archivo (emular “tail -2″).

sed '$!N;$!D'

Este también es complejo. Primero la dirección ‘$!’ restringe los comandos ‘N’ y ‘D’ a todas las líneas excepto la última.

Note como la dirección está negada. Si ‘$<comando>’ restringe el comando a la última línea, entonces ‘$!<comando>’ restringe el comando a todas las líneas excepto la última. Esto puede ser aplicado a todas las operaciones de restricción.

En este one-liner, el comando ‘N’ lee la línea siguiente desde la entrada y la agrega al pattern space. El comando ‘D’ elimina todo en el pattern space hasta el primer símbolo ‘\n’ (nueva línea). Estos dos comandos siempre mantienen solo las líneas leídas mas recientes dentro del pattern space. Cuando se procesa la anteúltima línea, ‘N’ es ejecutado y agrega la última línea al pattern space. ‘D’ no se ejecuta dado que ‘N’ ha consumido la última línea. En este momento sed termina e imprime las últimas dos líneas del archivo que estaban contenidas en el pattern space.

48. Imprimir la última línea de un archivo (emular “tail -1″).

sed '$!d'

Este one-liner descarta todas las líneas excepto la última. El comando ‘d’ elimina el pattern space actual, lee la siguiente línea y reinicia la ejecución de los comandos desde le principio. En este caso es un loop que irá borrando una a una las líneas hasta que llegue a la última. Debido a la restricción ‘$!’ no se elimina la última y así el pattern space es mostrado.

Otra manera de realizar lo mismo:

sed -n '$p'

El parámetro ‘-n’ suprime automáticamente la impresión del pattern space. Al colocar el comando explícito ‘p’ junto al final de archivo ‘$’ se obtiene el mismo resultado anterior.

49. Imprimir la anteúltima línea de un archivo.

Eric nos da 3 one-liners diferentes para hacer esta tarea. El primero imprime una línea en blanco si el archivo contiene tan sólo una línea:

sed -e '$!{h;d;}' -e x

En este caso se ejecutan los comandos ‘h;d’ para todas las líneas excepto la última: ‘$!{h;d}’. El comando ‘h’ coloca la línea actual en el hold buffer y luego ‘d’ borra la línea actual. Luego se vuelven a ejecutar los comandos h;d. Por cada línea, ésta se copia al hold buffer. En la última línea ‘h;d’ no se ejecuta. En este momento ‘x’ es ejecutado y se produce un intercambio del contenido del hold buffer con el pattern space. Si recuerdas, la anteúltima línea fue previamente almacenada en el hold buffer, ‘x’ hace el intercambio y sed termina imprimiéndola.

En el caso de un archivo con una sola línea, sólo el comando ‘x’ es ejecutado con lo cual el hold buffer que está vacío se intercambia con el pattern space y se imprime una línea en blanco.

El segundo one-liner imprime la primera línea si el archivo contiene sólo 1 línea:

sed -e '1{$q;}' -e '$!{h;d;}' -e x

Éste está dividido en dos partes. La primera parte ‘1{$q;}’ maneja el caso en el que el archivo contenga sólo una sola línea. La segunda parte ‘$!{h;d;} x’ es exactamente lo mismo que el one-liner anterior.

La primera parte dice que si es la primera línea ‘1’ entonces se ejecutará ‘$q’, este comando dice que si es la última línea, entonces termina. Lo cual es correcto si el archivo contiene una sola línea.

El tercer one-liner imprime un vacío en archivos de una sola línea:

sed -e '1{$d;}' -e '$!{h;d;}' -e x

Este también está dividido en dos partes. La primera es ‘1{$d;}’ y la segunda es exactamente lo mismo a los dos anteriores one-liners. La primera parte dice que si es la primera línea entonces se ejecuta ‘$d’, este comando dice que si es la última línea entonces se elimina el pattern space y se comienza otra vez. Como el archivo contiene una sola línea, no hay nada por hacer y sed termina imprimiendo una línea vacía.

50. Imprimir solo las líneas que igualan a la expresión regular (emular “grep”).

sed -n '/regexp/p'

Este one-liner utiliza el switch ‘-n’ para evitar mostrar las líneas y hace uso del comando ‘p’ para imprimir sólo aquellas líneas que concuerdan con ‘/regexp/’. Aquellas líneas que no concuerden serán descartadas y las que si serán impresas.

Otro one-liner que realiza lo mismo es:

sed '/regexp/!d'

En este caso se eliminan todas las líneas que no concuerdan con ‘/regexp/’. El ‘!’ antes del comando ‘d’ invierte la selección.

51. Imprime sólo las líneas que no concuerdan con la expresión regular (emular “grep -v”).

sed -n '/regexp/!p'

Este one-liner es el inverso del previo.

Este one-liner utiliza el switch ‘-n’ para evitar mostrar las líneas y hace uso de ‘!p’ para evitar imprimir aquellas líneas que si concuerden con ‘/regexp/’. Otro ejemplo para esto:

sed '/regexp/d'

En este caso se ejecuta el comando ‘d’ en todas las líneas en que igualen a ‘/regexp/’.

52. Imprimir la línea anterior a regexp, pero no la que la iguala.

sed -n '/regexp/{g;1!p;};h'

Este one-liner guarda cada línea en el hold buffer con el comando ‘h’. Si una línea concuerda con regexp, el hold buffer (que contenía la línea anterior) es copiado con el comando ‘g’ al pattern space y éste es impreso a través de ‘p’. El ‘1!’ restringe a ‘p’ para que no imprima la primera línea ya que no hay líneas anteriores.

53. Imprimir la línea siguiente a regexp, pero no la que la iguala.

sed -n '/regexp/{n;p;}'

Primero que nada se deshabilita la impresión con el switch ‘-n’. Luego para todas las líneas que concuerdan con ‘/regexp/’, se ejecutan los comandos ‘n’ y ‘p’. El comando ‘n’ es el único que depende de la bandera ‘-n’. Si ‘-n’ es especificado vaciará el pattern space actual y leerá la línea siguiente de la entrada. Si ‘-n’ no es especificado, imprimirá el pattern space actual antes de vaciarlo. En este caso se ‘n’ vacía el pattern space, lee la siguiente línea y luego ‘p’ imprime esa línea.

54. Imprimir una línea anterior y posterior a regexp. También imprimir la línea que concuerda con regexp y su número de línea (emular “grep -A1 -B1″).

sed -n -e '/regexp/{=;x;1!p;g;$!N;p;D;}' -e h

Primero veamos el comando ‘h’ al final del script. Se ejecuta en cada línea y guarda la línea del pattern space al hold buffer. La idea de guardar cada línea en el hold buffer es que si la siguiente línea concuerda con ‘/regexp/’ entonces la línea previa estará disponible en el hold buffer.

Ahora veamos lo complicado ‘/regexp/{=;x;1!p;g;$!N;p;D;}’. Se ejecuta sólo si las líneas concuerdan con ‘/regexp/’. La primera cosa que hace es imprimir el número de línea actual con el comando ‘=’. Luego intercambia el hold buffer con el pattern space con el comando ‘x’. Tal como se explicó, ‘h’ se asegura que el hold buffer siempre contenga la línea anterior. Luego, si no es la primer línea (comprobamos con ‘1!p’) imprimimos el pattern space que es efectivamente la línea anterior. Ahora el comando ‘g’ es ejecutado, copia la línea original que fue recién intercambiada con el hold buffer desde el pattern space. ‘$!N’ consulta que no sea la última linea. ‘N’ agrega la siguiente línea al pattern space y las separa con un ‘\n’. El pattern space ahora contiene la línea que concuerda con ‘/regexp/’ y la línea siguiente. El comando ‘p’ las imprime. Con ‘D’ borramos la línea actual (la que ha igualado a ‘/regexp/’) del pattern space y finalmente ‘h’ es ejecutado nuevamente, lo que coloca el contenido del pattern space en el hold buffer. Como ‘D’ borró la línea actual, la siguiente línea fue puesta en el hold buffer.

55. Grep para “AAA” y “BBB” y “CCC” en cualquier orden.

sed '/AAA/!d; /BBB/!d; /CCC/!d'

Este one-liner invierte el comando ‘d’ al ser ejecutado en líneas que no contienen ‘AAA’, ‘BBB’ y ‘CCC’. Si una línea no contiene alguno de estos items será eliminada y sed continúa con la línea siguiente. Sólo si los tres patrones están presentes, entonces sed imprimirá la línea.

56. Grep para “AAA” y “BBB” y “CCC” en ese orden.

sed '/AAA.*BBB.*CCC/!d'

Este one-liner elimina las líneas que no coincidan con la expresión regular ‘/AAA.*BBB.*CCC/’. Por ejemplo una línea ‘AAAfooBBBbarCCC’ será impresa pero ‘AAAfooCCCbarBBB’ no.

También se puede escribir como:

sed -n '/AAA.*BBB.*CCC/p'

57. Grep para “AAA” o “BBB”, o “CCC”.

sed -e '/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d

Este one-liner usa el comando ‘b’ para ir al final del script si la línea coincide con ‘AAA’ o ‘BBB’ o ‘CCC’. Al final del script la línea simplemente es impresa. Si la línea no coincide con algún patrón el script alcanza el comando ‘d’ que borra dicha línea.

También se puede utilizar gsed

gsed '/AAA\|BBB\|CCC/!d'
gsed -r '/AAA|BBB|CCC/!d'

o también

gsed -rn '/AAA|BBB|CCC/p'

58. Imprimir un párrafo que contenga ‘AAA’ (párrafos están separados por líneas en blanco).

sed -e '/./{H;$!d;}' -e 'x;/AAA/!d;'

Primero notemos que este one-liner está dividido en dos partes. La primera parte es ‘/./{H;$!d;}’ y la segunda es ‘x;/AAA/!d’.

En la primera parte encontramos el interesante patrón ‘/./’. ¿Qué crees que hace? Bien, una línea que separa párrafos sería una línea en blanco, es decir que no hay caracteres en ella. Este patrón iguala solo a las líneas que no separan párrafos. Con el comando ‘H’ las vamos agregando al hold buffer. Además prevenimos que sean mostradas con el comando ‘d’ (excepto porque ‘$!’ restringe a ‘d’ salvo para la última línea). Una vez que sed ve una línea en blanco, el patrón ‘/./’ ya no coincide y la segunda parte del one-liner es ejecutada.

La segunda parte intercambia el hold buffer con el pattern space usando el comando ‘x’. El pattern space ahora contiene un párrafo completo. A continuación sed testea si el párrafo contiene ‘AAA’. Si lo contiene sed no hace nada lo que resulta en la impresión del párrafo. Si no contiene ‘AAA’, sed ejecuta el comando ‘d’ el cuál borra el pattern space sin mostrarlo y recomienza.

59. Imprimir un párrafo si contiene ‘AAA’ y ‘BBB’ y ‘CCC’ en cualquier orden.

sed -e '/./{H;$!d;}' -e 'x;/AAA/!d;/BBB/!d;/CCC/!d'

Este one-liner está dividido también para mejor claridad. La primera parte es exactamente igual al one-liner anterior. La segunda parte es muy similar al #55.

El comando ‘x’ en la segunda parte intercambia el hold buffer que contiene el párrafo con el pattern space. A continuación se testean tres cosas: que el párrafo contenga ‘AAA’, ‘BBB’ y ‘CCC’. Si el párrafo no contiene alguno de ellos, el comando ‘d’ es ejecutado borrando el pattern space; si coinciden todos sed mostrará el párrafo y recomenzará.

60. Imprimir un párrafo si contiene ‘AAA’ o ‘BBB’ o ‘CCC’.

sed -e '/./{H;$!d;}' -e 'x;/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d

La primera parte es exactamente como los dos anteriores y no requiere explicación. La segunda parte es casi como #57.

El comando ‘x’ intercambia el párrafo almacenado en el hold buffer con el pattern space. Entonces se realiza el siguiente test: si el pattern space contiene ‘AAA’ sed continúa hasta el fin del script ejecutando el comando ‘b’ que mostraría el párrafo contenido. Si ‘AAA’ no coincide se testea la existencia de ‘BBB’, y lo mismo para ‘CCC’. Si ninguno de estos patrones coincide, sed ejecuta el comando ‘d’ eliminando todo y recomenzando desde el principio.

Aquí hay otra manera de realizar lo mismo con GNUsed:

gsed '/./{H;$!d;};x;/AAA\|BBB\|CCC/b;d'

61. Imprimir sólo las líneas que tengan 65 o más caracteres de longitud.

sed -n '/^.\{65\}/p'

Este one-liner imprime las líneas que tienen m65 o más caracteres de longitud. Se realiza usando la expresión regular ‘^.{65}’. Si la línea tiene menos de 65 caracteres, la expresión regular no coincide y la línea no se imprime (dado el switch ‘-n’).

62. Imprimir sólo las líneas que tengan menos de 65 caracteres.

sed -n '/^.\{65\}/!p'

Este one-liner invierte al anterior. Al utilizar ‘!p’.

Otra forma de realizar lo mismo:

sed '/^.\{65\}/d'

63. Imprimir una sección de un archivo desde una expresión regular hasta el final.

sed -n '/regexp/,$p'

Este one-liner usa ‘/regex/,$’. Si una línea coincide con regex, entonces será impreso el resto del archivo.

64. Imprimir las líneas 8-12 (inclusive) de un archivo.

sed -n '8,12p'

Este es otro tipo de rango. Se imprime una porción de un archivo entre dos números de línea. Otra forma:

sed '8,12!d'

65. Imprimir la línea número 52.

sed -n '52p'

Este one-liner restringe al comando ‘p’ a la línea 52. Sólo ésta línea se imprime. Y esta es otra forma de hacerlo:

sed '52!d'

y Aquí otra forma que va eliminando las líneas que no sean la 52 y cuando llega a ésta, sale.

sed '52q;d'

66. Comenzar en la línea 3, imprimir cada 7 líneas.

gsed -n '3~7p'

Este one-liner usa un tipo de ‘salto’. Está pensado para GNU sed (gsed). Comienza en la línea 3, deja pasar 6 líneas e imprime la septima línea (en este caso la 10 ya que comenzó en la 3), luego deja pasar otras 6 e imprime la siguiente (la 17) y así hasta el final de archivo.

Usando sed, sería:

sed -n '3,${p;n;n;n;n;n;n;}'

67. Imprimir una sección de líneas entre dos expresiones regulares (inclusive).

sed -n '/Iowa/,/Montana/p'

Este one-liner imprime todas las líneas entre la primera línea que coincide con la expresión regular ‘Iowa’ y la primera línea que coincide con la expresión regular ‘Montana’.

Usa un rango ‘/start/,/finish/’ mostrando todas las líneas que estén entre las líneas que coincidan con estas expresiones.

Una nota importante sobre los rangos: si ‘start’ y ‘finish’ coinciden sobre la misma línea, esto no funcionará. Por favor leer Sed FAQ 3.3 para mayor información.

l pattern space
About these ads

2 comentarios (+¿añadir los tuyos?)

  1. Trackback: Sed en una-línea (sed one-liners) Parte I « Jinete del Dragon
  2. Trackback: Famous Sed One-Liners Explained, Part II - good coders code, great reuse

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Archivos

julio 2009
D L M X J V S
« jun   sep »
 1234
567891011
12131415161718
19202122232425
262728293031  
Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 95 seguidores

%d personas les gusta esto: