miércoles, 27 de julio de 2016

sort

El programa sort ordena el contenido de la entrada estándar, o de uno o más archivos especificados en la línea de comandos, y manda el resultado a la salida estándar. Usando la misma técnica que usamos con cat, podemos demostrar el procesado de la entrada estándar directamente desde el teclado:

[me@linuxbox ~]$ sort > foo.txt
c
b
a
[me@linuxbox ~]$ cat foo.txt
a
b
c

Tras introducir el comando, escribimos las letras "c", "b" y "a", seguidas de nuevo de Ctrl-d para indicar el final del archivo. Después vemos el archivo resultante y comprobamos que ahora las líneas aparecen ordenadas.

Como sort puede aceptar múltiples archivos en la línea de comandos como argumentos, es posible unir múltiples archivos en un único archivo ordenado. Por ejemplo, si tenemos tres archivos de texto y queremos combinarlos en un único archivo ordenado, podríamos hacer algo como esto:

sort file1.txt file2.txt file3.txt > final_sorted_list.txt

sort tiene varias opciones interesantes. Aquí hay una lista parcial:

Tabla 20-1: Opciones comunes de ordenamiento
Opción Opción larga Descripción
-b --ignore-leading-blanks Por defecto, el ordenado se realiza en la línea completa, empezando con el primer carácter de la línea. Esta opción hace que sort ignore los espacios al principio de la línea y calcula el ordenado basándose en el primer carácter que no es un espacio en blanco en la línea.
-f --ignore-case Hace que el ordenamiento no distinga entre mayúsculas y minúsculas.
-n --numeric-sort Realiza el ordenado basado en la evaluación numérica de una cadena. Usar esta opción permite que el ordenado se realice según valores numéricos en lugar de valores alfabéticos.
-r --reverse Ordena inversamente. Los resultados son descendentes en lugar de ascendentes.
-k --key=campo1[,campo2] Orden basado en un campo clave localizado entre campo1 y campo2 en lugar de en la línea entera. Veremos este tema luego.
-m --merge Trata cada argumento como el nombre de un archivo preordenado. Une múltiples archivos en uno sólo ordenado resultante sin realizar ningún ordenamiento adicional.
-o --output=archivo Envía salida ordenada a archivo en lugar de a la salida estándar.
-t --field-separator=carácter Define el caracter separador de campos. Por defecto los campos se separan por espacios o tabuladores.

Aunque la mayoría de las opciones anteriores son bastante autoexplicativas, algunas no lo son tanto. Primero veamos la opción -n, usada para ordenamiento numérico. Con esta opción, es posible ordenar valores basándonos en valores numéricos. Podemos demostrarlo ordenando los resultados del comando du para determinar los usuarios con más espacio en disco. Normalmente, el comando du lista los resultados de un sumario por orden de la ruta:

[me@linuxbox ~]$ du -s /usr/share/* | head
252    /usr/share/aclocal
96     /usr/share/acpi-support
8      /usr/share/adduser
196    /usr/share/alacarte
344    /usr/share/alsa
8      /usr/share/alsa-base
12488        /usr/share/anthy
8      /usr/share/apmd
21440        /usr/share/app-install
48     /usr/share/application-registry

En este ejemplo, canalizamos el resultado en head para limitar los resultados a las primeras diez líneas. Podemos producir una lista ordenada numéricamente para mostrar los diez mayores consumidores de espacio de esta forma:

[me@linuxbox ~]$ du -s /usr/share/* | sort -nr | head
509940 /usr/share/locale-langpack
242660 /usr/share/doc
197560 /usr/share/fonts
179144 /usr/share/gnome
146764 /usr/share/myspell
144304 /usr/share/gimp
135880 /usr/share/dict
76508        /usr/share/icons
68072        /usr/share/apps
62844        /usr/share/foomatic

Usando las opciones -nr, producimos un orden numérico inverso, con los mayores valores apareciendo primero en los resultados. Este orden funciona porque los valores numéricos están al principio de cada línea. Pero, ¿qué pasa si queremos ordenar una lista basándonos en algún valor localizado dentro de la línea? Por ejemplo, los resultados de un ls -l:

[me@linuxbox ~]$ ls -l /usr/bin | head
total 152948
-rwxr-xr-x 1 root root  34824 2008-04-04 02:42 [
-rwxr-xr-x 1 root root 101556 2007-11-27 06:08 a2p
-rwxr-xr-x 1 root root  13036 2008-02-27 08:22 aconnect
-rwxr-xr-x 1 root root  10552 2007-08-15 10:34 acpi
-rwxr-xr-x 1 root root   3800 2008-04-14 03:51 acpi_fakekey
-rwxr-xr-x 1 root root   7536 2008-04-19 00:19 acpi_listen
-rwxr-xr-x 1 root root   3576 2008-04-29 07:57 addpart
-rwxr-xr-x 1 root root  20808 2008-01-03 18:02 addr2line
-rwxr-xr-x 1 root root 489704 2008-10-09 17:02 adept_batch

Ignorando, por el momento, que ls puede ordenar sus resultados por tamaño, podríamos usar sort para ordenar la lista por tamaño de archivo, así:

[me@linuxbox ~]$ ls -l /usr/bin | sort -nr -k 5 | head
-rwxr-xr-x 1 root root 8234216 2008-04-07 17:42 inkscape
-rwxr-xr-x 1 root root 8222692 2008-04-07 17:42 inkview
-rwxr-xr-x 1 root root 3746508 2008-03-07 23:45 gimp-2.4
-rwxr-xr-x 1 root root 3654020 2008-08-26 16:16 quanta
-rwxr-xr-x 1 root root 2928760 2008-09-10 14:31 gdbtui
-rwxr-xr-x 1 root root 2928756 2008-09-10 14:31 gdb
-rwxr-xr-x 1 root root 2602236 2008-10-10 12:56 net
-rwxr-xr-x 1 root root 2304684 2008-10-10 12:56 rpcclient
-rwxr-xr-x 1 root root 2241832 2008-04-04 05:56 aptitude
-rwxr-xr-x 1 root root 2202476 2008-10-10 12:56 smbcacls

Muchos usos de sort implican el procesado de datos tabulares, como los resultados del comando ls anterior. Si aplicamos terminología de bases de datos a la tabla anterior, diríamos que cada fila es un registro consistente en múltiples campos, como los atributos de archivo, contador de enlaces, nombre de archivo, tamaño de archivo y así sucesivamente. sort puede procesar campos individuales. En términos de bases de datos, podemos especificar uno o más campos clave para usar como claves de ordenamiento. En el ejemplo anterior, especificamos las opciones n y r para realizar un ordenamiento numérico inverso y especificamos -k 5 para hacer que sort use el quinto campo como la clave de ordenamiento.

La opción k es muy interesante y tiene muchas funciones, pero primero tenemos que hablar sobre cómo sort define los campos. Consideremos un archivo de texto muy simple consistente en una única línea que contenga el nombre del autor:

William Shotts

Por defecto, sort ve esta línea con dos campos. El primer campo contiene los caracteres:

"William"

y el segundo campo contiene los caracteres:

" Shotts"

lo que significa que los caracteres de espacio en blanco (espacios y tabuladores) se usan como delimitadores entre los campos y que los delimitadores se incluyen en el campo cuando se realiza el ordenado.

Mirando de nuevo una línea de la salida de nuestro ls, podemos ver que una línea contiene ocho campos y que el quinto campo es el tamaño del archivo:

-rwxr-xr-x 1 root root 8234216 2008-04-07 17:42 inkscape

Para nuestra próxima serie de experimentos, consideraremos el siguiente archivo que contiene la historia de tres distribuciones Linux populares desarrolladas entre 2006 y 2008. Cada línea del archivo tiene tres campos: el nombre de la distribución, el número de versión y la fecha de publicación en formato MM/DD/YYYY:

SUSE    10.2   12/07/2006
Fedora  10     11/25/2008
SUSE    11.0   06/19/2008
Ubuntu  8.04   04/24/2008
Fedora  8      11/08/2007
SUSE    10.3   10/04/2007
Ubuntu  6.10   10/26/2006
Fedora  7      05/31/2007
Ubuntu  7.10   10/18/2007
Ubuntu  7.04   04/19/2007
SUSE    10.1   05/11/2006
Fedora  6      10/24/2006
Fedora  9      05/13/2008
Ubuntu  6.06   06/01/2006
Ubuntu  8.10   10/30/2008
Fedora  5      03/20/2006

Usando un editor de texto (quizá vim), introduciremos estos datos y nombraremos al archivo resultante distros.txt.

A continuación, trataremos de ordenar el archivo y observaremos el resultado:

[me@linuxbox ~]$ sort distros.txt
Fedora  10    11/25/2008
Fedora  5     03/20/2006
Fedora  6     10/24/2006
Fedora  7     05/31/2007
Fedora  8     11/08/2007
Fedora  9     05/13/2008
SUSE    10.1  05/11/2006
SUSE    10.2  12/07/2006
SUSE    10.3  10/04/2007
SUSE    11.0  06/19/2008
Ubuntu  6.06  06/01/2006
Ubuntu  6.10  10/26/2006
Ubuntu  7.04  04/19/2007
Ubuntu  7.10  10/18/2007
Ubuntu  8.04  04/24/2008
Ubuntu  8.10  10/30/2008

Bien, casi ha funcionado. El problema aparece en el ordenamiento de los números de versión de Fedora. Como un "1" viene antes que un "5" en el juego de caracteres, la versión "10" aparece arriba mientras que la versión "9" aparece al final.

Para arreglar este problema vamos a tener que ordenar por múltiples claves. Queremos realizar un orden alfabético en el primer campo y luego un orden numérico en el segundo campo. sort permite múltiples instancias de la opción -k así que pueden especificarse múltiples claves de ordenamiento. De hecho, una clave puede incluir un rango de campos. Si no se especifica un rango (como ha sido el caso de nuestros ejemplos anteriores), sort usa una clave que comienza con el campo especificado y se extiende hasta el final de la línea. Aquí tenemos la sintaxis de nuestro ordenamiento multi-clave:

[me@linuxbox ~]$ sort --key=1,1 --key=2n distros.txt
Fedora   5    03/20/2006
Fedora   6    10/24/2006
Fedora   7    05/31/2007
Fedora   8    11/08/2007
Fedora   9    05/13/2008
Fedora   10   11/25/2008
SUSE     10.1 05/11/2006
SUSE     10.2 12/07/2006
SUSE     10.3 10/04/2007
SUSE     11.0 06/19/2008
Ubuntu   6.06 06/01/2006
Ubuntu   6.10 10/26/2006
Ubuntu   7.04 04/19/2007
Ubuntu   7.10 10/18/2007
Ubuntu   8.04 04/24/2008
Ubuntu   8.10 10/30/2008

Aunque hemos usado la forma larga de la opción para que se vea más claramente, -k 1, 1 -k 2n sería completamente equivalente. En la primera instancia de la opción de clave, especificamos un rango de campos a incluir en la primera clave. Como queremos limitar el orden al primer campo solamente, especificamos 1, 1 que significa "comienza en el campo uno y termina en el campo uno." En la segunda instancia, hemos especificado 2n, que significa que el campo 2 es la clave de ordenamiento y que el orden debería ser numérico. Puede incluirse una letra de opción al final de un especificador de clave para indicar el tipo de orden a realizar. Estas letras de opción son las mismas que las opciones globales del programa sort: b (ignora los espacios en blanco iniciales), n (orden numérico), r (orden inverso), y así sucesivamente.

El tercer campo de nuestra lista contiene una fecha con un formato inapropiado para ordenarlo. En los ordenadores, las fechas están normalmente formateadas como YYYY-MM-DD para hacer más fácil el ordenamiento cronológico, pero la nuestra está en formato Americano MM/DD/YYYY. ¿Cómo podemos ordenar esta lista por orden cronológico?

Afortunadamente, sort nos da una forma. La clave de opción permite especificar compensaciones (offsets) dentro de campos, por lo que podemos definir claves dentro de campos:

[me@linuxbox ~]$ sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt
Fedora  10    11/25/2008
Ubuntu  8.10  10/30/2008
SUSE    11.0  06/19/2008
Fedora  9     05/13/2008
Ubuntu  8.04  04/24/2008
Fedora  8     11/08/2007
Ubuntu  7.10  10/18/2007
SUSE    10.3  10/04/2007
Fedora  7     05/31/2007
Ubuntu  7.04  04/19/2007
SUSE    10.2  12/07/2006
Ubuntu  6.10  10/26/2006
Fedora  6     10/24/2006
Ubuntu  6.06  06/01/2006
SUSE    10.1  05/11/2006
Fedora  5     03/20/2006

Especificando -k 3.7 le decimos a sort que use una clave de ordenación que comience en el séptimo carácter del tercer campo, que corresponde al principio del año. De igual forma, especificamos -k 3.1 y -k 3.4 para aislar las zonas del día y el mes de la fecha. También añadimos las opciones n y r para realizar un ordenado numérico inverso. La opción b se incluye para suprimir los espacios iniciales (cuyo número varía según la línea, afectando de este modo al resultado del ordenado) en el campo fecha.

Algunos archivos no usan tabuladores ni espacios como delimitadores de campos; por ejemplo, el archivo /etc/passwd:

[me@linuxbox ~]$ head /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh

Los campos en este archivo están delimitados por dos puntos (:), así que ¿cómo ordenamos este archivo usando un campo clave? sort cuenta con la opción -t para definir el carácter separador de campos. Para ordenar el archivo passwd por el séptimo campo (el shell por defecto de la cuenta), podríamos hacer esto:

[me@linuxbox ~]$ sort -t ':' -k 7 /etc/passwd | head
me:x:1001:1001:Myself,,,:/home/me:/bin/bash
root:x:0:0:root:/root:/bin/bash
dhcp:x:101:102::/nonexistent:/bin/false
gdm:x:106:114:Gnome Display Manager:/var/lib/gdm:/bin/false
hplip:x:104:7:HPLIP system user,,,:/var/run/hplip:/bin/false
klog:x:103:104::/home/klog:/bin/false
messagebus:x:108:119::/var/run/dbus:/bin/false
polkituser:x:110:122:PolicyKit,,,:/var/run/PolicyKit:/bin/false
pulse:x:107:116:PulseAudio daemon,,,:/var/run/pulse:/bin/false

Especificando el carácter de los dos puntos como separador de campos, podemos ordenar por el séptimo campo.

No hay comentarios:

Publicar un comentario