lunes, 21 de agosto de 2017

Comandos agrupados y subshells

bash permite agrupar comandos. Esto puede hacerse de dos formas; con un comando agrupado o con un subshell. Aquí tenemos ejemplos de la sintáxis de cada uno de ellos:

Comando agrupado:

{ comando1; comando2; [comando3; ...] }

Subshell:

(comando1; comando2; [comando3;...])

Las dos formas difieren en que el comando agrupado rodea sus comandos con llaves y el subshell usa paréntesis. Es importante fijarse en que, debido a la forma en que bash implementa los comandos agrupados, las llaves deben separarse de los comandos por un espacio y el último comando debe terminar con un punto y coma o con una nueva línea antes de la llave de cierre.

Entonces ¿para qué sirven los comandos agrupados y los subshells? Aunque tienen una diferencia importante (que veremos en un momento), ambos se usan para gestionar redirecciones. Consideremos un segmento de script que realiza redirecciones en múltiples comandos:

ls -l > output.txt
echo "Listing of foo.txt" >> output.txt
cat foo.txt >> output.txt

Esto es bastante sencillo. Tres comandos con su salida redireccionada a un archivo llamado output.txt. Usando un comando agrupado, podríamos codificarlo de la siguiente forma:

{ ls -l; echo "Listing of foo.txt"; cat foo.txt; } > output.txt

Usando un subshell es similar:

(ls -l; echo "Listing of foo.txt"; cat foo.txt) > output.txt

Usando esta técnica nos hemos ahorrado algo de escritura, pero donde brilla un comando agrupado o un subshell realmente es en las tuberías. Cuando construimos una tubería de comandos, a menudo es útil combinar el resultado de varios comandos en una única secuencia. Los comandos agrupados y los subshell hacen esto de forma fácil:

{ ls -l; echo "Listing of foo.txt"; cat foo.txt; } | lpr

Aquí hemos combinado la salida de nuestros tres comandos y la hemos canalizado a la entrada de lpr para producir un informe impreso.

En el script que sigue, usaremos comandos agrupados y veremos varias técnicas de programación que pueden emplearse junto con arrays asociativos. Este script, llamado array-2, cuando se el da el nombre de un directorio, imprime una lista de los archivos en el directorio junto a los nombres de los propietarios de los archivos y de los grupos propietarios. Al final del listado, el script imprime un recuento del número de archivos que pertenecen a cada propietario y grupo. Aquí vemos el resultado (resumido para abreviar) cuando se le da al script el directorio /usr/bin:

[me@linuxbox ~]$ array-2 /usr/bin
/usr/bin/2to3-2.6           root  root
/usr/bin/2to3               root  root
/usr/bin/a2p                root  root
/usr/bin/abrowser           root  root
/usr/bin/aconnect           root  root
/usr/bin/acpi_fakekey       root  root
/usr/bin/acpi_listen        root  root
/usr/bin/add-apt-repository root  root
.
.
.
/usr/bin/zipgrep            root  root
/usr/bin/zipinfo            root  root
/usr/bin/zipnote            root  root
/usr/bin/zip                root  root
/usr/bin/zipsplit           root  root
/usr/bin/zjsdecode          root  root
/usr/bin/zsoelim            root  root

File owners:
daemon  :    1 file(s)
root    : 1394 file(s)

File group owners:
crontab :    1 file(s)
daemon  :    1 file(s)
lpadmin :    1 file(s)
mail    :    4 file(s)
mlocate :    1 file(s)
root    : 1380 file(s)
shadow  :    2 file(s)
ssh     :    1 file(s)
tty     :    2 file(s)
utmp    :    2 file(s)

Aquí tenemos un listado (con los números de línea) del script:

 1 #!/bin/bash
 2
 3 # array-2: Use arrays to tally file owners
 4
 5 declare -A files file_group file_owner groups owners
 6
 7 if [[ ! -d "$1" ]]; then
 8    echo "Usage: array-2 dir" >&2
 9    exit 1
10 fi
11
12 for i in "$1"/*; do
13    owner=$(stat -c %U "$i")
14    group=$(stat -c %G "$i")
15    files["$i"]="$i"
16    file_owner["$i"]=$owner
17    file_group["$i"]=$group
18    ((++owners[$owner]))
19    ((++groups[$group]))
20 done
21
22 # List the collected files
23 { for i in "${files[@]}"; do
24    printf "%-40s %-10s %-10s\n" \
25       "$i" ${file_owner["$i"]} ${file_group["$i"]}
26 done } | sort
27 echo
28
29 # List owners
30 echo "File owners:"
31 { for i in "${!owners[@]}"; do
32     printf "%-10s: %5d file(s)\n" "$i" ${owners["$i"]}
33 done } | sort
34 echo
35
36 # List groups
37 echo "File group owners:"
38 { for i in "${!groups[@]}"; do
39     printf "%-10s: %5d file(s)\n" "$i" ${groups["$i"]}
40 done } | sort

Echemos un vistazo a la mecánica de este script:

Línea 5: Los arrays asociativos deben crearse con el comando declare usando la opción -A. En este script creamos los cinco arrays siguientes:

files contiene los nombres de los archivos en el directorio, indexados por nombre de archivo
file_group contiene el grupo propietario de cada archivo, indexado por nombre de archivo
file_owner contiene el propietario de cada archivo, indexado por nombre de archivo
groups contiene el número de archivos pertenecientes al grupo indexado
owners contiene el número de archivos pertenecientes al propietario indexado

Líneas 7-10: Comprueba que se ha pasado un nombre de directorio válido como parámetro posicional. Si no, se muestra un mensaje de uso y el script sale con un estado de salida de 1.

Líneas 12-20: Hace un bucle a través de los archivos del directorio. Usando el comando stat, las líneas 13 y 14 extraen los nombres del propietario del archivo y del grupo propietario y asigna valores a sus arrays respectivos (líneas 16, 17) usando el nombre del archivo como índice del array. Del mismo modo, el nombre del archivo se asigna al array files (línea 15).

Líneas 18-19: El número total de archivos pertenecientes al propietario del archivo y al grupo propietario se incrementan en uno.

Líneas 22-27: Se muestra la lista de archivos. Esto se hace usando la expansión de parámetros "${array[@]}" que se expande en la lista completa de elementos del array cada uno tratado como una palabra separada. Esto permite la posibilidad de que un nombre de archivo contenga espacios en blanco. Fíjate también que el bucle completo está incluido entre llaves para que forme un comando agrupado. Esto permite que la salida completa del bucle se canalice al comando sort. Esto es necesario porque la expansión de los elementos del array no está ordenada.

Líneas 29-40: Estos dos bucles son similares al bucle de la lista de archivos excepto que usan la expansión "${!array[@]}" que se expande en la lista de índices del array en lugar de en la lista de elementos del array.

No hay comentarios:

Publicar un comentario