Mas cosas guays sobre el comando “SED”

Copy&page:http://programacionbizarra.blogspot.com/2009/03/presentacion-del-sed-copyright-c-2009.html

Tutorial, Guía o Simplemente Introducción al Comando Sed de Unix

 

Copyright (c) 2009 Héctor Francisco Hernández <[email protected]>.

Se otorga permiso para copiar, distribuir y/o modificar este documento bajo los términos de la Licencia de Documentación Libre de GNU, Versión 1.3 o cualquier otra versión posterior publicada por la Free Software Foundation; sin Secciones Invariantes ni Textos de Cubierta Delantera ni Textos de Cubierta Trasera. Una copia de la licencia se encuentra en http://www.gnu.org/copyleft/fdl.html.

 

1. Introducción

 

La herramienta de Unix "sed" ("stream editor" o "editor de flujo") es, junto con "awk" (y sin
contar a Perl, la panacea, en la contienda), la más poderosa en lo que
a procesamiento de texto plano se refiere.

 

Al igual que el editor "ed", del cual toma todos sus comandos, permite escribir pequeños scripts de edición de texto en un
particular lenguaje:

 

# el comando "i" inserta texto en la línea
# que se le indique
sed '7i hola mundo' zaraza.txt

# el comando "d" borra líneas
sed '1d' zaraza.txt

# el comando "s" sustituye texto utilizando
# expresiones regulares si se desea
sed '1,$s/palavra/palabra/g' zaraza.txt

 

Sin embargo, su característica distintiva es la habilidad de procesar
texto proveniente desde la entrada estándar. La que lo convierte en una
herramienta ideal para construir filtros y utilizarlos en nuestros scripts
Bourne, Korn, tcsh, bash, o lo que fuere.

 

2. Uso Básico

 

Las instrucciones que entiende "sed" están compuestas primero por el
número o rango de líneas sobre el cual se realizará la transformación y
luego por la acción a realizar. Omitir el número o rango de líneas
implicará que la acción se ejecute sobre la totalidad de las mismas.

 

De este modo si, por ejemplo, disponemos del siguiente listado obtenido por el
comando "ps":

 

 PID   TTY TIME     CMD
6245 pts/1 00:00:00 bash
6246 pts/1 00:00:00 ps

 

Y para procesar el listado queremos suprimir los títulos,
podríamos eliminar la primera línea con la instrucción
"1d". Por lo tanto nuestro comando en el shell sería
"ps | sed ‘1d’" y obtendríamos:

 

6245 pts/1 00:00:00 bash
6246 pts/1 00:00:00 ps

 

Los rangos de líneas son de la forma "n,m" que significa
"desde la línea n inclusive hasta la línea m".
Pudiendo "m" ser el símbolo especial "$" que
indica el final del archivo.

 

Así, por ejemplo, si quisiéramos, como generalmente ocurre,
obtener desde la línea 74 hasta la 83 de un archivo de texto denominado
"xxx" podríamos hacerlo con la instrucción
"74,83p" así:

 

sed -n '74,83p' xxx

 

En este ejemplo debo hacer notar el parámetro "-n". Por
defecto "sed" imprime todo a la salida estándar, sólo
que "-n" modifica este comportamiento para que no lo haga. Por lo
tanto, si no usáramos el parámetro "-n"
obtendríamos todo el archivo "xxx" con las
líneas desde la 74 hasta la 83 duplicadas, ya que la instrucción
"p" volvería a imprimirlas.

 

No sólo es posible indicar un número de línea o un rango numérico,
también es posible indicar una expresión regular o un rango de
expresiones regulares.

 

Cuando queremos que la acción se ejecute sobre las líneas que
coincidan con una expresión determinada, debemos escribirla delante del
comando entre barras:

 

# imprime sólo las líneas que tengan la
# palabra "proc", lo mismo que haría el
# comando "grep proc"
sed -n '/proc/p'

# borra lo que se encuentre entre
# "#ifdef __WIN32__" y "#endif"
sed '/#ifdef __WIN32__/,/#endif/d'

 

De este modo, por ejemplo, si disponemos del siguiente listado arrojado por
"ls":

 

total 888
-rw-r--r--  1 usuario1 users   2737 2003-04-01 04:36 LICENSE.txt
-rw-r--r--  1 usuario1 users   3639 2003-04-01 04:36 README.txt
drwxr-xr-x  4 usuario1 users   4096 2007-09-08 21:52 build
drwxr-xr-x  2 usuario1 users   4096 2007-09-08 21:52 convert
drwxr-xr-x  5 usuario1 users   4096 2007-09-08 21:52 docs
drwxr-xr-x 12 usuario1 users   4096 2007-09-08 21:52 examples
drwxr-xr-x  3 usuario1 users   4096 2003-04-01 04:35 src
drwxr-xr-x 12 usuario1 users   4096 2003-04-01 04:36 test
-rw-r--r--  1 usuario1 users 351405 2003-04-01 04:36 velocity-1.3.1.jar
-rw-r--r--  1 usuario1 users 510105 2003-04-01 04:36 velocity-dep-1.3.1.jar
drwxr-xr-x  5 usuario1 users   4096 2007-09-08 21:52 xdocs

 

Y queremos obtener un listado sólo de los directorios (las filas que
comienzan con la letra "d"). Podríamos utilizar el sed para
borrar las entradas que no comienzan con esa letra:
"ls -l | sed ‘/^d/!d’":

 

drwxr-xr-x  4 usuario1 users 4096 2007-09-08 21:52 build
drwxr-xr-x  2 usuario1 users 4096 2007-09-08 21:52 convert
drwxr-xr-x  5 usuario1 users 4096 2007-09-08 21:52 docs
drwxr-xr-x 12 usuario1 users 4096 2007-09-08 21:52 examples
drwxr-xr-x  3 usuario1 users 4096 2003-04-01 04:35 src
drwxr-xr-x 12 usuario1 users 4096 2003-04-01 04:36 test
drwxr-xr-x  5 usuario1 users 4096 2007-09-08 21:52 xdocs

 

Note que el símbolo de exclamación "!" detrás
de la expresión regular niega la condición. Esto debería
leerse como "borre todas las líneas que no comiencen con la letra
d".

 

Otra forma de obtener el mismo resultado es imprimiendo sólo
las líneas que comienzan con la letra "d":

 

ls -l | sed -n '/^d/p'

 

Explicar las expresiones regulares va más allá de los
límites de este artículo, pero el carácter "^"
significa "comienzo de línea", por lo tanto "^d"
hace referencia a las líneas que poseen una "d" seguida de
su comienzo.

 

3. Lo Más Importante: El Comando "s"

 

El comando "s" sustituye expresiones regulares en la línea actual.
Este comando por sí sólo sería justificación para
la existencia del sed.

 

El modo de uso de este comando es "s/patrón/reemplazo/flags". Donde
"patrón" es la expresión regular que debe sustituirse por
"reemplazo" y los flags son modificadores opcionales que indican, por ejemplo,
si la búsqueda debe discriminar entre mayúsculas y
minúsculas o no.

 

Siguiendo mi uso de explicar con ejemplos, suponga que posee un texto donde
las palabras podrían estar separadas por más de un espacio:

 

Aquí   me pongo a   cantar
al  compás de    la vigüela,
que al hombre  que lo desvela
una   pena     estrordinaria,
como la ave solitaria
con    el cantar se   consuela.

 

Sin embargo usted necesita, por alguna extraña razón que ahora no
se me ocurre, que estén separadas por exactamente un único
espacio. Podría sustituir el patrón "uno o más espacios"
por un único espacio. El comando sería:

 

sed 's/ \+/ /g'

 

El "\+" luego de un carácter significa una o más ocurrencias de
ese carácter (se recomienda leer sobre expresiones regulares en general).
La letra "g" al final es un flag que hace que se sustituyan todos los patrones
encontrados en cada línea. Sin él sólo se
sustituirá la primera ocurrencia en cada renglón del texto.

 

El resultado:

 

Aquí me pongo a cantar
al compás de la vigüela,
que al hombre que lo desvela
una pena estrordinaria,
como la ave solitaria
con el cantar se consuela.

 

Y así también es posible hacer referencia al "patrón" o a
"subpatrones" en el "reemplazo" mediante los carácteres especiales
"\&" y "\1", "\2", "\3"… "\n" respectivamente. Por ejemplo, suponga que
posee la siguiente lista en la que cada elemento es de la forma
"[email protected]":

 

[email protected]/src/hardware
[email protected]/src/libs
[email protected]/include
[email protected]/docs
[email protected]/visualc_net

 

Luego de aplicarle a la lista "sed ‘s_\(.*\)@\(.*\)_\2/\1_’", obtenemos:

 

dosbox-0.70/src/hardware/libhardware.a
dosbox-0.70/src/libs/Makefile.in
dosbox-0.70/include/video.h
dosbox-0.70/docs/README.video
dosbox-0.70/visualc_net/Makefile

 

Esto significa que "\1" hace referencia al subpatrón que se encuentra
entre los primeros paréntesis y que "\2" hace referencia al
subpatrón entre los segundos paréntesis. Fíjese
también que en lugar de separar los parámetros del comando con
una barra "/" lo hice con "_". Esto es porque sed nos permite utilizar cualquier
carácter como separador y se dará cuenta automáticamente
de cuál estamos usando.

 

Aunque el ejemplo que acabo de enseñar parece estúpido,
créame que es real y que para un trabajo me ha tocado hacer un script
que leía una lista con esa estructura.

 

4. Procesando Varias Líneas por Vez

 

Hasta el momento en todos los ejemplos que mostré se leía una
línea, se chequeaba una condición y se realizaba una
operación. Y así cada operación involucraba una
única línea.

 

Existen, sin embargo, circunstancias en las que es necesario operar sobre
más de una línea por vez. Observe el siguiente archivo:

 

Registro: 1
Nombre: Anibal
Telefono: 621-229

Registro: 2
Nombre: Hector
Telefono: 562-245

Registro: 3
Nombre: Pablo
Telefono: 622-354

 

Alguien ha decidido guardar una lista de personas con sus números
de teléfonos en un extraño formato, utilizando cuatro
líneas por registro.
Nuestra tarea es poner la información en una manera más
conveniente para su procesamiento:

 

1;Anibal;621-229
2;Hector;562-245
3;Pablo;622-354

 

Claro está que sería imposible
solucionar este problema leyendo de a una línea a la vez. Sería
bueno poder trabajar leyendo de a un registro (cuatro líneas) por
iteración.

 

Este tipo de problemas se resuelve con el comando "N", que fuerza
la lectura
de la siguiente línea en la iteración actual.

 

Al comenzar la iteración, sed trae la línea al buffer.
Internamente el valor del buffer sería:

 

buffer = "Registro: 1"

 

Luego de ejecutar la instrucción "N", que lee la
línea siguiente y la concatena en el buffer, quedaría:

 

buffer = "Registro: 1\nNombre: Anibal"

 

Observe que "\n" significa "nueva línea"
(el carácter ASCII número 10).

 

Como vemos, nos vamos aproximando a la solución. Basta con leer
dos líneas más y ya tenemos el registro completo.
Y luego de hacerlo debemos reemplazar el retorno de carro "\n"
por ";" con "s/\n/;/g", para así llegar a:

 

buffer = "Registro: 1;Nombre: Anibal;Telefono: 621-229;"

 

Ahora resta quitar lo que molesta, las palabras
"Registro: ", "Nombre: ", "Telefono: " y el
punto y coma de más al final.

 

Para lograrlo utilizaremos una super sustitución con expresiones
regulares:

 

"s/^Registro: \(.*\);Nombre: \(.*\);Telefono: \(.*\);$/\1;\2;\3/"

 

buffer = "1;Anibal;621-229"

 

Ahora podríamos poner todo junto.
Suponiendo que los datos originales estaban en el archivo "agenda",
nuestro comando quedaría:

 

sed 'N;N;N;s/\n/;/g;s/^Registro: \(.*\);Nombre: \(.*\);Telefono: \(.*\);$/\1;\2;\3/' agenda

 

Ilegible, ¿verdad? Si así lo considera, puede hacerlo en dos
pasos invocando dos veces al sed.

 

5. Programación en Sed

 

En la documentación del sed de GNU, el capítulo que explica esto dice
"Comandos para gurúes del sed … En la mayoría de los casos, el uso de
estos comandos indican que probablemente sea mejor programar en awk o Perl".
La razón por la cual enseño estos comandos es para mostrar el límite de la herramienta.

 

A pesar de no tener variables ni poseer forma de controlar el flujo de
ejecución más que con etiquetas, un comando para "GOTO" y otro
para "GOTO" condicional, sed es un lenguaje "Turing-completo". Esto es que
tiene un poder computacional equivalente a la máquina universal de
Turing. Por eso circula un mito nerd de que en algún lugar de la web
se puede descargar los fuentes de un sokoban, un arkanoid y algún otro
juego clásico implementado en sed.

 

Los comandos para hacer un "GOTO" incondicional y condicional son "b" y "t"
respectivamente. Las etiquetas se definen con el comando ":". Además
sed posee un buffer denominado "hold space" que provee un espacio adicional para
almacenar datos sobre el cual podremos guardar y recuperar lo que pongamos con
los comandos h, H, g, G, x. Refiéranse a la documentación del sed
los interesados, pues no tengo interés en explicar sobre este asunto.

 

Imaginese que sin variables, sin estructuras de control y sin operadores
aritméticos entre otras cosas,
la programación en sed no es más que otro aburrido juego de
ingenio para simios. Por este motivo será que finalizaré este
capítulo copiándole un script del tutorial "Sed – An Introduction and
Tutorial" de Bruce Barnett que ilustra algo de esto y dejaré el tema
allí mismo.

 

Lo que hace el ejemplo es guardar los párrafos en el "hold buffer" a
medida que los va leyendo. Si el párrafo posee el patrón pasado
por parámetro al script lo imprime.

 

#!/bin/sh
sed -n '
# Si es una línea vacía, finaliza el párrafo.
# Por lo tanto se debe analizar. Se hace un "GOTO" a "para".
/^$/ b para
# Si la línea no está vacía, se agrega al "hold buffer".
H
# Al final del archivo se analiza el último párrafo.
# "GOTO" a "para".
$ b para
# Se hace un "GOTO" al final del script.
b
# La etiqueta "para". Sección donde se analiza el párrafo.
:para
# Se recupera el párrafo completo.
x
# Se busca el patrón, si se encuetra se imprime el párrafo.
/'$1'/ p
'

 

6. Apéndice A: Quitando Tags XML. El Criterio "The Longest Match"

 

Para mostrar cómo se hace haré el siguiente ejercicio. Suponga
el siguiente código HTML:

 

<h2>Contenido</h2>
<ul>
<li class="toclevel-1">1 <span class="toctext">Historia</span></li>
<li class="toclevel-1">2 <span class="toctext">Ventajas del XML</span></li>
<li class="toclevel-1">3 <span class="toctext">Estructura del XML</span></li>
</ul>

 

Para obtener el texto sin los tags podría acceder a la página
ya procesada por el navegador web, copiar el texto con Ctrl-c y pegarlo con
Ctrl-v en mi editor favorito (las teclas pueden cambiar según el entorno
en el que estemos trabajando). Pero aquí, como mi intención es
mostrar el sed, lo haré con esta herramienta.

 

Lo que se me ocurre es sustituir el patrón "<.*>"
por una cadena vacía. Sin embargo el resultado obtendido luego de
ejecutar "sed ‘s/<.*>//g’ temp" es varias líneas
vacías.

 

Lo que pasa es que el patrón es ambiguo. Por ejemplo para
"<h2>Contenido</h2>" sed comenzaría leyendo desde
el primer "<", pero podría detenerse en el ">"
detrás del primer h2 o en el ">" detrás del segundo
h2. Y los creadores de la herramienta han decidido que se tome siempre la
coincidencia más larga, "the longest match".

 

Una solución al problema sería, por ejemplo, utilizar el
patrón "<[^<]*>". "[^<]" significa
cualquier caracter que no sea "<". En ese caso el resultado
sería:

 

Contenido
1 Historia
2 Ventajas del XML
3 Estructura del XML

 

7. Apéndice B: El Editor "ed"

 

Los comandos del sed provienen del editor más primitivo de Unix, llamado
"ed". Este editor, a diferencia de sed que es un editor de flujo, es
un editor común y corriente… bueno, casi. A diferencia de los editores
modernos no es "WYSIWYG" (What You See Is What You Get, o "lo que
vés es lo que obtenés"), o sea que no nos muestra el texto
que estamos editando, simplemente nos permite escribir comandos (más o
menos los mismos que el sed) de manera interactiva.

 

El uso de este editor no es común en estos tiempos, ya que la
mayoría de los programadores y administradores de Unix prefieren trabajar
con el "vi". Sin embargo, el ed puede aprovecharse como otro comando
para nuestros scripts cuando necesitamos abrir un archivo, hacer una
edición y volver a grabarlo. En ese caso no precisamos un editor de
flujo como el sed, sino más bien alguno que edite el archivo en el lugar.

 

Volvamos al ejemplo de eliminar los espacios sobrantes.

Aquí   me pongo a   cantar
al  compás de    la vigüela,
que al hombre  que lo desvela
una   pena     estrordinaria,
como la ave solitaria
con    el cantar se   consuela.

 

Imagínese que el texto se encuentra en el archivo "prueba.txt".
Si lo resolviésemos con el sed, como hicimos en el ejemplo pasado,
obtendríamos el resultado en la salida estándar o en otro archivo. Sin
embargo queremos que los datos permanezcan en "prueba.txt" luego de la
transformación. Por lo que tenemos dos opciones: a) obtener los datos
en un archivo auxiliar y luego reemplazar "prueba.txt" por este
archivo; o b) editar el archivo en el lugar utilizando el "ed".

 

El comando para editar el archivo en "ed" es el mismo que en el
"sed" pero con tres salvedades. La primera es que "ed" no
tiene como rango implícito todas las líneas del archivo, por lo
que sí o sí tenemos que indicarle el rango
"1,$" delante del comando. La segunda es que luego de la
transformación debemos ejecutar el comando "w" para grabar
el archivo, ya que "ed" no lo hará automáticamente.
Y la tercera es que los comandos se separan con una nueva línea, y no
con un ";".

 

"ed" recibe el nombre del archivo como parámetro y lee los
comandos desde la entrada estándar. También requiere la
opción "-n" para suprimir molestos mensajes. Por lo que
quedaría de la siguiente manera:

 

ed -s prueba.txt <<< $'1,$s/ \+/ /g\nw'

 

Pero algunas implementaciones modernas del "sed", como por ejemplo la
de GNU, también poseen la opción "-i" que permite
modificar el archivo en el lugar y obtener el mismo resultado:

 

sed -i 's/ \+/ /g' prueba.txt

 

8. Final

 

"sed" se encuentra siempre entre mi reducido y selecto repertorio de
herramientas de trabajo. Podría decir que está entre las muy
primeras opciones a la hora de realizar cualquier tipo de transformación
sobre textos extensos y que no conozco otra cosa que lo haga mejor.

 

Además de eso he tenido la oportunidad de operar en varios sistemas
tipo Unix distintos y todos poseen la herramienta. Y cuando me encontré
trabajando con sistemas de otro tipo, a falta de alguna mejor opción, he
tenido la necesidad de instalarla. Dicho sea de paso, he conseguido
implementaciones libres en la internet que se instalan fácilmente y sin
ningún tipo de problema.

 

La herramienta es más bien compacta, sencilla y poderosa. Por lo que se
logra dominar luego de usarla un par de veces, siempre y cuando se esté
familiarizado con las expresiones regulares. Y si no lo está es muy
recomendable que lea algún tutorial pequeño sobre el asunto y lo
ponga en práctica.

 

Hasta aquí espero que haya obtenido una idea acerca del "sed" y
del provecho que le puede sacar tanto para crear scripts o para transformar
todo tipo de textos.

Para más información sugiero que lea la documentación de

 

la implementación en su sistema operativo. Si tiene GNU instalado

 

escriba "info sed". También encontrará muchos textos

 

escritos en todos los idiomas en la internet, lo que es un punto a favor

 

del uso del "sed".

Mas cosas guays sobre el comando “SED”
0 votes, 0.00 avg. rating (0% score)

About this entry