Sed

De Jose Castillo Aliaga
Ir a la navegación Ir a la búsqueda

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.


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

daypatrones de expresiones regulares de búsqueda de patrones

nightcadena 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 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:

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:


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/'


Ejercicios:


1- Modifica la IP de un ordenador por la dirección de la red. (por ejemplo de 192.168.3.5 a 192.168.3.0)

$ echo 192.168.0.5 | sed 's/.[0-9]\+$/.0/'

2- Cambia del PATH, el /usr/bin por /home/lliurex/bin

$ echo $PATH | sed 's_/usr/bin_/home/lliurex/bin_'

3- Pon una palabra determinada entre comillas

$ echo 'El so Windows' | sed 's/so/"&"/'

4- Pon una coma entre la primera palabra y las demás de una línea:

$ echo pienso luego existo | sed 's/[a-z]\+/&,/'

5- Convierte 123456789abc en una dirección MAC

$ echo 123456789abc | sed -e 's/../&:/g' -e 's/:$//'

6- De todas las líneas de /etc/passwd, muestra el directorio home y luego el nombre

$ cat /etc/passwd | sed 's/^\([A-Za-z0-9]\+\):[^:]*:[^:]*:[^:]*:[^:]*:\(.*\):.*/\2 \1/g'

$ cat /etc/passwd | cut -d":" -f1,6 | sed 's/\(.*\):\(.*\)/\2 \1/'


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.


/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


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.

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/bashsed 's/a/A/g s/e/E/g s/i/I/g s/o/O/g s/u/U/g' <old >new


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 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


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.

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]*$///^$/ dp

}'


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/shsed '/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.


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.


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.

Mostra 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

A partir de aquí es sólo un borrador

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


Ejemplos

sed -n 1p archivo# Devuelve la primera linea y (-n) sin el texto originalsed -n 4,6p archivo# Devuelve de las lineas 4 a la 6sed -n '4,$p' archivo# Devuelve de la linea 4 al final *sed -n /^E/ archivo# Devuelve todas las lineas que empiezan con "E" (grep)sed -n '/^E/,$p' archivo# desde la 1ª linea que empieza con "E" al finalsed 's/antes/despues/g' archivo # Sustituye todos los "antes" por "despues"sed 's/antes/despues/' archivo # = pero solo el primero de cada lineased '1,3s/antes/despues/g' archivo # = pero solo en las 3 primeras lineassed '/patron/s/antes/despues/g' archivo # = pero solo en lineas con ese patronsed '/p1/,/p2/s/antes/despues/g' archivo # = pero con patron p1 en linea previased 'y/[123]/[456]/' archivo# Sustituye los "1" por "4", los "2" por "5"...* Las '' son necesarias para que el shell no sustituya $p como una variable


EDITANDO UN ARCHIVO:cp archivo archivo.oldsed -e 'comandos' \ -e 'mas-comandos' \ archivo.old >archivo #OPCIONALif [ -s archivo ]then rm -f archivo.oldfiCOMANDOS UTILES:# comentar una linea-e 's/patron-a-dejar-comentado/#&/'# & = texto coincidente con el patron# borrar una linea-e '/patron/ d'# añadir al final (tras la última linea)-e '$ a\primera linea añadida\segunda linea añadida'# insertar _ANTES_ de la última linea-e '$ i\primera linea añadida\segunda linea añadida'# añadir tras una cierta linea (si no existe esa linea no hace nada)-e '/patron-de-la-linea/ a\linea añadida'ATENCION: Los patrones de direccion /patron/ no admiten agrupamiento con ()