ant

FILTROS
3.14

sig

3.14.1 Introducción

Kem Thompsom y Denis Ritchie cuando crearón Unix a principios de los sesenta tuvieron en mente crear un sistema operativo que facilitase la labor a los programadores. A la conclusión que llegaron es que la mejor forma es establecer un conjunto definido de pequeñas herramientas que hagan una determinada labor muy bien. A partir de la unión de esas herramientas se pueden realizar labores más complejas. Los resultados de unas se conviertan en entradas de otra.

Esa forma de trasmitirse la información se realiza con el uso de la entrada y salida estandar ( pantalla y teclado ). Gracias a la existencia de las tuberias y redireciones se consiguen esos resultados.

Se puede apreciar en un ejemplo. Si un usuario introduce esta linea:
$ who | grep pepe
whogrep son programas distintos separados por la tubería "|" . who nos muestra un listado de todos los usuarios conectados al ordenador en el momento dado. Su salida habitual sería algo parecido a esto:
$ who
tyfa tty1 Ago 22 13:15
pepe ps/2 Ago 22 14:36
root tty2 Ago 22 10:03
pepe ps/2 Ago 22 14:37
Lo que devuelve who se divide en 4 campos separados por tabuladores. Los cuales son nombre de usuario (login), terminal en el que esta conectado, fecha y hora de inicio de la conexión.

"grep pepe" por su parte busca las lineas de esa salida que contengan la secuencia "pepe".
De esta forma la salida es:
$ who | grep pepe
pepe ps/2 Ago 22 14:36
pepe ps/2 Ago 22 14:37
Por ejemplo si ahora el usuario en cambio quiere algo más simple, es decir saber si está o no está conectado mediante el número de conexiones que tiene en este momento se debe usar la utilidad wc. wc es contador de letras,palabras y lineas. En este caso solo precisamos saber el número de lineas así que se ha de utilizar la opción -l
$ who | grep pepe | wc -l
2
pepe esta conectado en 2 terminales
Si ahora probamos con antonio
$ who | grep antonio | wc -l
0
antonio no esta conectado

3.14.2 grep, egrep, fgrep

En el ejemplo anterior hemos visto básicamente cual es el objetivo de grep.
grep básicamente funciona de esta manera:

$ grep [-opciones] patrón ficheros

Las opciones más comunes son:

-n antepone a cada linea emparejada con su número de línea (útil para buscar algo en un fichero muy largo y saber donde esta especificamente)
-c imprime solo el número de coincidencia encontradas
-v solo se buscan las no coincidencias ( cuando lo que buscamos es lo que no coincide con el patrón )

El patrón es cualquier conjunto de caracteres que se busca. Hay que indicar es que si van acompañados de un espacio grep confundira el patrón con los ficheros a buscar, eso se soluciona con la ayuda de los caracteres ".

Por ejemplo:
$ grep "Hola mundo" fichero
Si buscamos alguna cadena que contenga un comodín , apóstrofes , comillas , redirreciones o barras del tipo "\", se deberá anteponer una barra "\" para asi indicar de que buscamos esa caracter en si y no la substitución del comodín , o que iniciamos una cadena de varias palabras.
$ grep \*\"\?\<  fichero
Esto es una cadena chunga -> *"?<

3.14.3 Sed

sed es una de las herramientas más peculiares de Unix. sed significa Stream Editor (o sea editor de flujo). Los editores suelen aceptar de forma interactiva las modificaciones que se desean insertar. Sed nos permite crear pequeños programas "shell scripts" parecidos a los batch de MS-DOS. Sed nos da la capacidad de modificar de forma automática el contenido de un fichero, permitiendonos crear shell scripts que lo modifiquen "on the fly". Las capacidades de este editor son muy completas.

Sed suele llamarse de esta forma:
$ sed 'comando-sed' fichero
Por ejemplo tengamos un fichero en el que deseamos reemplazar todos las apariciones de "Manolo" por "Fernando".
Manos a la obra:
$ sed 's/Manolo/Fernando/g' archivo
Y devuelve por salida estandar las modificaciones. Si se desea conservar el resultado se redigirá con ">"

Los usuarios de vi reconecerán inmediatamente que se trata de un comando típico de vi de busqueda y sustitución. En realidad los comandos de tipo ":" (los que invocan a ex) se pueden usar con sed.

La estructura de las ordenes sed consiste en indicar primero una cadena (o secuencia de cadenas) sobre las que actuar y luego el comando. Para indicar una cadena se puede indicar un número, un intervalo de numeros o buscar un patrón.

Los comandos usuales de sed:

a\ añade las lineas siguientes a las lineas afectadas
c\ cambia las lineas afectadas por las lineas siguientes
d borra las lineas afectadas
g hace sustituciones generales, de todos los patrones localizados inserta en lugar de limitarse al primero
i\ inserta las lineas siguientes a las afectadas
p imprime la línea, incluso con la opción -n
q abandonada (quit) cuando se alcanza la línea especificada
r fichero lee un fichero , añadiendo el contenido a la salida
s/uno/dos sustituye la cadena "uno" por "dos"
w fichero copia esa linea a otro fichero
= imprime el número de línea
! comando aplica un comando a dicha línea

Con sed se puede especificar sobre que lineas o conjunto de lineas se aplica el comando:
$ sed '3d' archivo
Borrará la tercera línea del archivo
$ sed '2,4s/e/#/' fichero
Substituye el caracter e por # en las lineas del 2 a la 4 inclusive.

También es posible realizar comandos sobre lineas que contengan una determinada cadena haciendo uso, si se desea, de expresiones regulares.
$ sed '/[Qq]ueen/d' canciones
Borra todas las lineas que contengan la cadena "Queen" o "queen".

Con la ayuda de expresiones regulares podemos por ejemplo eliminar las lineas vacias de un fichero.
$ sed '/^$/d' archivo
Aunque esto no borrará las cadenas que contengan espacios. Con esta versión además se consigue ese propósito.
$ sed '/^ *$/d' archivo
La secuencia ' *' indica que se debe buscar cualquier combinación de cero o mas apariciones del patrón ' '.
$ sed '/InitMenu/a\
> gvim gvim.xpm exec gvim &' .xvwmrc
Este ejemplo buscaría una linea que contenga la cadena InitMenu y después de esa le añadiria esa cadena.

Cabe indicar de que awk y sed forman pieza clave de los shell scripts más complejos. Puede resultar realmente impresionante lo que se puede llegar a hacer sin hacer uso para nada de C o cualquier otro lenguaje compilado. Cabe destacar que por ejemplo el setup de la distribucion de slakware así como muchos CGI del web son en realidad shell scripts.

Últimamente el uso de las herramientas de la línea de comandos ha sido algo denostado, achacandole demasiada antiguedad para los entornos de ventanas que se usan actualmente así como la llegada de lenguaje Perl que pretende ser un substituto de shell scripting dan a indicar que estas herramientas están condenadas al olvido. En mi experiencia he podido comprobar que muchas aplicaciones ( incluso un pequeño gestor de base de datos ) es cuestión de pocas líneas de código en shell script.

Es aquí donde awk junto con sed pueden realizar una gran labor sobre información almacenada en formato ASCII. Con ellos podemos realizar labores iguales a la suma de un pequeño gestor de base de datos más una hoja de cálculo.

Supongamos una factura en la que se indica en un fichero los artículos comprados y su precio de venta al público. Por ejemplo este archivo "compras":
naranjas 5 250
peras 3 120
manzanas 2 360
Se trata de un fichero con 3 campos separados por tabuladores. Ahora se desea crear un cuarto campo con el precio total por cada producto.
$ awk '{total=$2*$3; print $0 , total }' compras
naranjas 6 250 1250
peras 3 120 360
manzanas 2 360 720
total es una variable al que se le asignan los valores multiplicados de los campos segundo y tercero, luego a cada linea se imprime la linea completa ($0) y el total por linea.

Veremos awk en la sección 4.14

3.14.4 cut y paste

En Unix la información solía guardarse en ficheros de texto ASCII organizados por campos verticales separados con un caracter diferenciador, este solia ser un tabulador o el carácter ":". Una de las necesidades que se suele producir en estos casos es separar los campos de un fichero y unirlos en otro. cut y paste realizan ese trabajo.

Vamos a usar como ejemplo el fichero /etc/passwd encargado de gestionar los usuarios. Su contenido se compone de 7 campos separados por ":" . Los campos son respectivamente y en este orden login,clave encriptada, identificación de usuario, identificación de grupo, información del usuario, directorio de inicio de usuario y shell que usa.

He aquí un extracto típico de ese archivo.

decker:x:500:500:Óscar Carrascosa Blanco:/home/murie:/bin/bash
root:x:0:0:root:/root:/bin/bash
practica:x:501:501:Usuario de practicas para Ksh:/home/practica:/bin/ksh
wizardi:x:502:502:Wizard para nethack:/home/wizard:/bin/bash
Si por ejemplo ahora queremos ahora emparejar los usuarios con la shell que usa debemos cortar los campos 1 y 7.
Manos a la obra:
$ cut -f1,7 -d: /etc/passwd

decker:/bin/bash
root:/bin/bash
practica:/bin/ksh
wizard:/bin/bash
-f expecifica los campos a visualizar -d indica el separador a usar ( por defecto el tabulador ) y por último se encuentra el fichero a listar.
También es posible especificar intervalos de campos:
$ cut -f5-7 -d: /etc/passwd

Óscar Carrascosa Blanco:/home/decker:/bin/bash
root:/root:/bin/bash
Usuario de practicas para Ksh:/home/practica:/bin/ksh
Wizard para nethack:/home/wizard:/bin/bash
Ahora supongamos que hemos redirigido la salida con la ayuda de ">" a 2 ficheros y queremos unir las 2 salidas. Para ello esta paste.
$ paste salida1 salida2

decker:/bin/bash:Óscar Carrascosa Blanco:/home/decker:/bin/bash
root:/bin/bash:root:/root:/bin/bash
practica:/bin/ksh:Usuario de practicas para Ksk:/home/practica:/bin/ksh
wizard:/bin/bash:Wizard para nethack:/home/wizard:/bin/bash

3.14.5 Sort

Supongamos ahora que deseamos de un /etc/passwd ordenada según la información de los usuarios. Para ello recurrimos a sort, la herramienta de ordenación.
$ sort -t: +4 /etc/passwd

murie:x:500:500:Manuel Muriel Cordero:/home/murie:/bin/bash
practica:x:501:501:Usuario de practicas para Ksh:/home/practica:/bin/ksh
wizard:x:502:502:Wizard para nethack:/home/wizard:/bin/bash
root:x:0:0:root:/root:/bin/bash
Se puede apreciar que ha ordenado la salida, pero siguiendo el criterio de la tabla ASCII, si deseamos que no diferencia entre mayúsculas y minúsculas se haría con:
$ sort -t: +4f /etc/passwd

murie:x:500:500:Manuel Muriel Cordero:/home/murie:/bin/bash
root:x:0:0:root:/root:/bin/bash
practica:x:501:501:Usuario de practicas para Ksh:/home/practica:/bin/ksh
wizard:x:502:502:Wizard para nethack:/home/wizard:/bin/bash
-t es la opción que indica que separador se usa.
+4 se refiere a cuantos campos hay que saltar antes de iniciar la ordenación.
f indica que se ignore las diferencias entre mayúsculas y minúsculas

Se puede realizar una ordenación mucho más refinada. Por ejemplo ordenado primero de forma inversa según el shell que use y luego por la iformación del usuario:
$ sort -t: +6r +4f /etc/passwd

practica:x:501:501:Usuario de practicas para Ksh:/home/practica:/bin/ksh
murie:x:500:500:Manuel Muriel Cordero:/home/murie:/bin/bash
root:x:0:0:root:/root:/bin/bash
wizard:x:502:502:Wizard para nethack:/home/wizard:/bin/bash
Ahora supóngase que disponemos un fichero en el que se archiva las personas y las deudas en cuestión de préstamos que tiene con usted. Un ejemplo "manga":
$ sort -t: +6r +4f /etc/passwd

Son Goku:23450
Son Gohan:4570
Picolo:356700
Ranma 1/2:700
Si usted desea saber a cual primero le tiene que mandar el matón :-) deseará obtener una salida ordenada según morosos.

Si se realiza esta prueba se puede observar que:
$ sort +1 deudas

Ranma 1/2:700
Son Gohan:4570
Son Goku:23450
Picolo:356700
Esto no es precisamente lo que se espera debido a que se ha realizado una busqueda alfabética con un número diferente de digitos. La solución reside en la opcion n:
$ sort +1n deudas

Picolo:356700
Son Goku:23450
Son Gohan:4570
Ranma 1/2:700
Las opciones básicas de sort son estas:

+n.mse salta los n primeros campos y los m siguientes caracteres antes de empezar la ordenación.
-n.m para la ordenación al llegar al n-esimo campo y m caracteres despues

y como modificadores de los parámetros son:

-b ignora espacios en blanco iniciales
-d orden de diccionario ( solo se tienen en cuenta letras , numeros y espacios)
-f ignora la distinción entre mayúsculas y minúsculas.
-n orden aritmético
-r orden inverso

3.14.6 wc

wc como se ha visto ya es un contador de letras,lineas y palabras. Por defecto al introducir como parametro un fichero da como salida el número de lineas, palabras y caracteres que lo componen

Con las opciones podemos variar esa salida:
-l solo da las lineas
-w solo da las palabras
-c solo da los caracteres

3.14.7 Herramientas de diferenciación: cmp, comm y diff

A veces es necesario saber en que se diferencián 2 versiones de un fichero. Este se usa especialmente en programación cuando hay varias personas trabajando en un mismo proyecto modificando los fuentes de los programas. Si se precisa saber las variaciones de una versión de otra se recurre a estas herramientas.

cmp es la más básica de todas. Compara dos ficheros e indica, si la hay, el lugar donde se produce la primera diferencia (número de caracter y línea de la diferencia)

$ cmp antigua nueva
antigua nueva differ: char 11234, line 333
comm es algo más avanzado. Su salida se produce en 3 columnas. La primera contiene las lineas únicas del primer fichero, la segunda las únicas del segunda y la tercera las comunes. Dispone de parametros númericos que establecen si deseas eliminar alguna de esas columnas. Son la -1 , -2 y -3 que indican a comm que no visualizen la primera, segunda y tercera columna. Con este ejemplo se observa las lineas que sólo están en el primer fichero y las comunes.

$ comm -2 antigua nueva
Por último está diff. Es una herramienta fundamental en programación de proyectos avanzados. Si alguna vez ha bajado un kernel para compilarlo sabrá que puede bajarse los fuentes de la nueva o bajarse el patch con respecto a la anterior, que suele ser un fichero más pequeño . Ese patch suele terminar en .diff lo que indica que es el resultado de una salida diff. Esta herramienta contiene una serie de comandos de editor ( vi , rcs ) de tal manera que se hagan idénticos los 2 ficheros. También es aplicable a directorios y todos los archivos que lo contienen. La utilidad es muy clara, al tener que bajar menor cantidad de fuente ( solo los cambios ) se aplica el parche ( con patch ) y se vuelve a compilar. Sin parametros la salida específica en estos formatos como se deben hacer los cambios de tal manera que el primero sea igual al segundo con comandos vi.
$ diff antigua nueva
3c3
< El Hobbit
---
> El Señor de los Anillos
78a79,87
> Tres anillos para los Reyes Elfos bajo el cielo.
> Siete pera los Señores Enanos en casas de piedra.
> Nueve para los Hombres Mortales condenados a morir.
> Uno para el Señor Oscuro, sobre el trono oscuro
> en la tierra de Mordor donde se extienden las Sombras.
> Un Anillo para gobernarlos a todos. Un Anillo para encontrarlos,
> un anillo para atraerlos a todos y atarlos a las tinieblas
> en la tierra de Mordor donde se extienden las Sombras.
3c3 expresa que se debe cambiar la línea 3 , quitando "El Hobbit" y substituyendolo por "El Señor de los Anillos".
78a79,87 indica que se debe insertar unas nuevas lineas 79 a la 87.

3.14.8 uniq

uniq es el encargado de eliminar las redundancias. Por ejemplo si deseamos obtener un listado de la gente conectada al ordenador en un momento dado deberemos hacer uso de who y de cut.
$ who | cut -f1 -d' '
decker
decker
root
tyfany
El resultado no es del todo perfecto. Falta eliminar la doble aparición de decker. Dicho y hecho.
$ who | cut -f1 -d' ' | uniq
decker
root
tyfany
Como nota indicar que el -d' ' indica que el separador es el espacio en blanco ya que who no separa usando tabuladores.