3. El editor de flujo sed.
sed es un editor de textos no interactivo que contiene al editor de líneas ed. Procesa el fichero desde el comienzo hasta el final y no permite la selección de órdenes de edición una vez invocado. No se guarda el fichero a editar en un buffer así que puede manejar ficheros de cualquier tamaño.
sed [-f|e|n] 'lista de comandos de ed' file/s | ||
-f | Lee los comandos ed desde un fichero. El nombre del fichero a continuación de la opción es el que leerá. | |
-e | Coloca varias órdenes de edición en la propia línea (un -e por cada orden). | |
-n | Suprime la salida normal de todas las líneas del fichero y sólo aparecen en la salida las líneas especialmente solicitadas con una operación de imprimir p o l (como p pero muestra los caracteres especiales). Sin la opción -n una salida p (ya la vermos posteriormente) duplica las líneas seleccionadas. |
Aquí aplica los comandos de la lista, por orden, a cada renglón y escribe el resultado en la salida estándar. Los ficheros no se modifican y el resultado del comando se da a la salida estándar. Todas estas opciones pueden combinarse unas con otras.
Como recordatorio, un comando ed tiene, en general, la forma
[linea[,linea]]operacion[parametro]
donde la operación puede ser buscar, cambiar, añadir, imprimir, etc. Por comodidad incluye un carácter especial \n para representar una nueva línea.
UNA NOTA IMPORTANTE
A lo largo de todos estos ejercicios, veremos que los parámetros de sed van entrecomillados con el carácter " y no el '. La razón de esto es muy simple. El comando sed admite ambos formatos, pero el shell puede evaluar las variables en las comillas dobles.
Por ello, si usamos "$VAR", sed utiliza el valor de la variable VAR pero '$VAR' lo tomaría literalmente como $VAR. Todo esto se verá muy claramente en el curso sobre el shell.
sed [opciones] "/patron/accion" file
Busca un patrón y realiza una acción sobre la línea encontrada. Hemos utilizado el carácter / para separar el patrón de búsqueda, pero podemos utilizar cualquiera que no aparezca en el patrón (como - ó ,). Se suele utilizar / por coherencia con ed.
sed [opciones] "s/patron1/patron2/X" file
Busca un patrón y lo sustituye por otro. Aquí, el carácter X puede ser g (que realiza la sustitución en todas las apariciones de patron1 en la línea) o ser un valor numérico n (que realiza la sustitución de la n-aparición del patron1 en la línea).
3.4 Transformar cadenas de caracteres.
sed [opciones] "y/cadena1/cadena2/"
La longitud de la primera cadena ha de ser igual a la de la segunda y no pueden haber rangos. Obviamente no es tan potente como tr.
sed [opciones] "comando_ed d" file
Borra la línea a la que se refiere el comando ed.
3.6 Escritura sobre otros ficheros.
sed [opciones] "w file" file
Guarda la salida sobre file.
sed [opciones] "r file1" file2
Lee la entrada desde file1 y file2. Si no existiera file1 no aparecería error y sed continuaría con el file2 de entrada normal.
sed [opciones] "comando_ed q" file
Finaliza en cuanto acaba el comando ed.
3.9 Trabajo sobre varios ficheros a la vez.
Se pueden especificar varios ficheros como entrada de un comando sed de forma que la salida será la suma de todos. Si no se especifica nigún fichero, se toma como entrada la entrada estándar.
3.10 No correspondencia de patrón.
Hasta ahora sed efectúa su trabajo sobre todas las líneas del fichero. Esto puede invertirse precediendo la operación por el carácter de admiración (!) que hace que el comando se realice sobre todas las líneas que no correspondan a las indicadas. Así, para saber todos los directorios que no son del grupo gestion
$ sed -n /gestion/!p ll.lis > sed.out
Y para saber todos los directorios que no son del grupo gestion ni users
$ sed -n /gestion/!p ll.lis | sed -n /users/!p
drwxr-xr-x 3 root root 1024 Abr 1 1993 delia
Notar que esto no podría hacerse así
$ sed -n -e /gestion/!p -e /users/!p ll.lis > sed.out
o de forma más comprimida
$ sed -n -e "/(gestion|users)/!p" ll.lis > sed.out
pues en ambos casos el fichero sed.out contendría los mismos datos que ll.lis.
3.11 Añadir líneas al fichero.
Pueden añadirse líneas a un fichero con los comandos a, i ó c. Dichos comandos (al contrario que con ed) deben ir seguidos por \ antes de añadir líneas, cada una de las cuales deberá también ir seguidas por \. Así
$ sed '$a\
> nueva linea1\
> nueva linea2\
> nueva linea' file_origen > file_destino
añade tres líneas a la salida del fichero origen.
Con i se inserta en una posición dada. Por ejemplo, para insertar una nueva línea en la segunda del fichero origen
$ sed '2i\
> nueva lineai' file_origen > file_destino
Por último con c se cambia la línea o líneas dadas por las siguientes (puede ser varias por una o una por varias).
Hasta ahora hemos hecho cambios sobre todo el fichero. Sin embargo es posible referirse a líneas de él. Estas pueden referirse de forma absoluta (así 2,24 son las líneas que van desde la 2 hasta la 24) o en forma de contexto (/AMAL/,/SANCHO/ líneas que van desde la primera aparición de la palabra AMAL hasta la primera aparición de SANCHO a partir de la anterior). Podemos combinar valores absolutos con expresiones obtenidas de contexto.
sed tiene dos buffers que guardan líneas del fichero. El primero, que llamaremos espacio patrón, guarda la línea sobre la que estamos trabajando. El segundo, que llamaremos espacio retén, guarda una línea elegida. Por defecto ambos están en blanco (aparecería como una línea en blanco).
Hay varias operaciones que mueven los espacios patrón y retén
head [-c|-l] [-n num] file ... | ||
h | Reemplaza el texto del espacio retén con el del espacio patrón. | |
H | Añade el texto del espacio patrón después de cada línea del espacio retén. (Ni en este caso, ni en el anterior se suprime el texto del espacio patrón.) | |
g | Reemplaza el espacio patrón por el retén. | |
G | Añade el espacio retén después de cada línea del espacio patrón. | |
x | Intercambia los contenidos de ambos espacios. |
Como ejemplo explicativo veamos el resultado de los siguientes comandos
$ sed -e /gestion/h -e '$G' ll.lis > out.lis
$ tail -3 out.lis
drwxrwxr-x 2 titulos gestion 1024 Mar 7 13:00 titulos
drwxrwxr-x 2 vetez gestion 1024 Mar 23 14:13 vetez
drwxrwxr-x 2 vetez gestion 1024 Mar 23 14:13 vetez
que busca la línea en que aparece gestion, la guarda en el espacio retén (borrando lo que haya anteriormente al ser la opción h) y la añade al final del fichero. El resultado sería una salida de todo ll.lis con la última línea repetida.
NOTA. Aquí hay que poner la última operación entre comillas simples debido a que el shell intentaría encontrar la variable G y, al no estar definida seguramente, haría que no se ejecutara nada.
Otro ejemplo es el siguiente
$ sed -e /users/H -e /users/d -e '$G' ll.lis > out.lis
$ tail -3 out.lis
drwxr-xr-x 2 nieves users 1024 Feb 22 1993 nieves
drwxr-xr-x 2 pardos users 1024 Jul 23 1992 pardos
drwxr-xr-x 2 rafa users 1024 Jun 11 1993 rafa
que guarda en el espacio retén todas líneas en que aparece users (añadiéndolas), las borra de la salida y finalmente las pone al final. Notar que al haber al principio una línea en blanco dentro del espacio retén tendremos una línea en blanco seguida de las líneas que aparece users. Aquí tenemos un ejercicio
$ sed -e /users/H -e /users/d -e '$G' ll.lis | sed -e '/^$/d' > out.lis
Podemos preparar guiones de comandos ed que puedan ser parámetros del programa sed. En ellos pueden incluirse varios comandos seguidos que podrán ser ejecutados. El programa sed realiza sobre los comandos un preproceso de forma que evalúa en qué orden deben ejecutarse. Así, antes de insertar o realizar una búsqueda, borrará las líneas que se pidan, etc.
3.15 Pero, ¿en qué línea estamos?
Para saber en qué línea nos encontramos utilizamos el significado especial del carácter =. Al contrario que en ed, aquí no es posible hacer una referencia relativa a una línea actual. Así, no podríamos decirle que cambiara la línea anterior a una dada por otra. Los datos que contiene la línea se representan simbólicamente por el metacarácter &.El problema del significado especial del carácter = es que sólo tiene valor especial en el caso de ser una acción a ejecutar. Por ello el comando
$ sed -n "s/.*/= &/p" ll.lis > out.lis
no pone el número de línea en cada línea. La salida sería algo como
= drwxr-xr-x 2 abad gestion 2048 May 6 1993 abad
= drwxrwx--- 2 acceso gestion 1024 Mar 22 11:47 acceso
= drwxr-xr-x 2 administ gestion 1024 Feb 16 15:42 administ
(etc)
Aunque hay una forma (bastante complicada) de conseguir con sed cosas como
1 drwxr-xr-x 2 abad gestion 2048 May 6 1993 abad
2 drwxrwx--- 2 acceso gestion 1024 Mar 22 11:47 acceso
3 drwxr-xr-x 2 administ gestion 1024 Feb 16 15:42 administ
(etc)
veremos que esto lo hacen otros programas de forma mucho más eficiente.
3.16 EJEMPLOS DEL COMANDO sed.
Para borrar el usuario titulos y cambiar el shell de trabajo ksh por csh en los usuarios del volumen /ext0_1 podríamos hacer algo como
$ sed -e /titulos/d -e /ext0_1/s/ksh/csh/ passwd.lis > out.lis
Para borrar las líneas de un fichero que sólo contienen tabuladores o espacios en blanco o están vacías (aquí ^I es el control que representa el carácter tabulador) haremos
$ sed "/^[ ^I]*$/d" file > out.lis
Para imprimir todas las líneas del fichero titulos.lis hasta el alumno de apellido DIAZ haremos (como queremos que sea primer apellido y no queremos que pueda aparecer un alumno con segundo appelido DIAZ lo buscamos precedido por un carácter de separador de campo)
$ sed -n "1,/^IDIAZ/p" titulos.lis > out.lis
Otro problema sería conocer en qué líneas están aquellos alumnos que no tienen título
$ sed -n "/N^IN/=" titulos.lis
3
4
67
(etc)
Si quisiéramos obtener algo más bonito podríamos hallar primero qué líneas son y combinar su resultado con un cambio global de éste
$ sed -n '/N^IN/=' titulos.lis | sed -n 's/.*/Error en línea: &/p'
Error en línea: 3
Error en línea: 4
(etc)
Si tuviéramos varios ficheros de entrada, podríamos extraer datos de los tres a la vez. Por ejemplo si quisieramos ver las apariciones del NIP 314150 en los archivos cab_expediente.dat y personas_actual.dat (veremos que esto es mucho más rápido con la utilidad grep)
$ sed -n /314150/p cab_expediente.dat personas_actual.dat > 314150.dat
Podemos añadir una línea a continuación de la primera aparición de 'Jose Santos' en un fichero de nombres
$ sed '/Jose Santos/a\
> Juan Santos' nombres > nombres.new
Puede verse que las operaciones siguientes son equivalentes
$ tail +12 titulos.lis > out.lis
$ sed -n "1,11d" titulos.lis > out.lis
$ sed -n '12,$p' titulos.lis > out.lis
(Obviamente la primera opción es mucho más rápida. Debemos utilizar comillas simples para que no se interprete $p como el valor de la variable p)