103.4 Lección 2
Certificación: |
LPIC-1 (101) |
---|---|
Versión: |
5.0 |
Tema: |
103 Comandos GNU y Unix |
Objetivo: |
103.4 Usar flujos, tuberías y redireccionamientos |
Lección: |
2 de 2 |
Introducción
Un aspecto de la filosofía de Unix establece que cada programa debe tener un propósito específico y no debe tratar de incorporar características fuera de su alcance. Pero mantener las cosas simples no significa resultados menos elaborados, ya que se pueden encadenar diferentes programas para producir un resultado combinado. El caracter de barra vertical |
, también conocido como el símbolo pipe, se puede usar para crear una tubería que conecte la salida de un programa directamente a la entrada de otro, mientras que command substitution permite almacenar la salida de un programa en una variable o usarlo directamente como argumento para otro comando.
Tuberías (Pipes)
A diferencia de los redireccionamientos, con las tuberías los datos fluyen de izquierda a derecha en la línea de comandos y el objetivo es otro proceso, no una ruta del sistema de archivos, un descriptor de archivo o un documento. El carácter de canalización |
le dice al shell que inicie todos los comandos distintos al mismo tiempo y que conecte la salida del comando anterior a la entrada del siguiente comando, de izquierda a derecha. Por ejemplo, en lugar de utilizar redireccionamientos, el contenido del archivo /proc/cpuinfo
enviado a la salida estándar por cat
puede canalizarse al stdin de wc
con el siguiente comando:
$ cat /proc/cpuinfo | wc 208 1184 6096
En ausencia de una ruta a un archivo, wc
cuenta el número de líneas, palabras y caracteres que recibe en su stdin, como es el caso en el ejemplo. Muchas tuberías pueden estar presentes en un comando compuesto. En el siguiente ejemplo, se utilizan dos tuberías:
$ cat /proc/cpuinfo | grep 'model name' | uniq model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
El contenido del archivo /proc/cpuinfo
producido por cat /proc/cpuinfo
se canalizó al comando grep 'model name'
, que luego selecciona solo las líneas que contienen el término model name
. La máquina que ejecuta el ejemplo tiene muchas CPU, por lo que hay líneas repetidas con model name
. La última tubería conecta el nombre del modelo grep 'model name'
a uniq
, que es responsable de omitir cualquier línea igual a la anterior.
Las tuberías se pueden combinar con redireccionamientos en la misma línea de comando. El ejemplo anterior se puede reescribir en una forma más simple:
$ grep 'model name' </proc/cpuinfo | uniq model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
La redirección de entrada para grep
no es estrictamente necesaria ya que grep
acepta una ruta de archivo como argumento, pero el ejemplo demuestra cómo construir dichos comandos combinados.
Las tuberías y redirecciones son exclusivas, es decir, una fuente puede asignarse a un solo destino. Sin embargo, es posible redirigir una salida a un archivo y aún verlo en la pantalla con el programa tee
. Para hacerlo, el primer programa envía su salida al stdin de tee
y se le proporciona un nombre de archivo a este último para almacenar los datos:
$ grep 'model name' </proc/cpuinfo | uniq | tee cpu_model.txt model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz $ cat cpu_model.txt model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
El resultado del último programa de la cadena, generado por uniq
, se muestra y almacena en el archivo cpu_model.txt
. Para no sobrescribir el contenido del archivo proporcionado sino para agregarle datos, la opción -a
debe proporcionarse a tee
.
Solo la salida estándar de un proceso es capturada por una tubería. Digamos que debe pasar por un largo proceso de compilación en la pantalla y al mismo tiempo guardar tanto la salida estándar como el error estándar en un archivo para su posterior inspección. Suponiendo que su directorio actual no tiene un Makefile, el siguiente comando generará un error:
$ make | tee log.txt make: *** No targets specified and no makefile found. Stop.
Aunque se muestra en la pantalla, el mensaje de error generado por make
no fue capturado por tee
y el archivo log.txt se creó vacío. Se debe hacer una redirección antes de que una tubería pueda capturar el stderr:
$ make 2>&1 | tee log.txt make: *** No targets specified and no makefile found. Stop. $ cat log.txt make: *** No targets specified and no makefile found. Stop.
En este ejemplo, el stderr de make
se redirigió al stdout, por lo que tee
pudo capturarlo con una tubería, mostrarlo en la pantalla y guardarlo en el archivo log.txt
. En casos como este, puede ser útil guardar los mensajes de error para su posterior inspección.
Sustitución de comando
Otro método para capturar la salida de un comando es command substitution (sustitución de comando). Al colocar un comando dentro de las comillas inversas, Bash lo reemplaza con su salida estándar. El siguiente ejemplo muestra cómo usar el stdout de un programa como argumento para otro programa:
$ mkdir `date +%Y-%m-%d` $ ls 2019-09-05
La salida del programa date
, la fecha actual formateada como año-mes-día, se utilizó como argumento para crear un directorio con mkdir
. Se obtiene un resultado idéntico usando $()
en lugar de comillas inversas:
$ rmdir 2019-09-05 $ mkdir $(date +%Y-%m-%d) $ ls 2019-09-05
El mismo método puede usarse para almacenar la salida de un comando como una variable:
$ OS=`uname -o` $ echo $OS GNU/Linux
El comando uname -o
genera el nombre genérico del sistema operativo actual, que estaba almacenado en la variable de sesión OS
. Asignar la salida de un comando a una variable es muy útil en los scripts, ya que permite almacenar y evaluar los datos de muchas maneras distintas.
Dependiendo de la salida generada por el comando reemplazado, la sustitución del comando incorporado puede no ser apropiada. Un método más sofisticado para usar la salida de un programa como argumento de otro programa emplea un intermediario llamado xargs
. El programa xargs
usa los contenidos que recibe a través de stdin para ejecutar un comando dado con los contenidos como argumento. El siguiente ejemplo muestra xargs
ejecutando el programa identify
con argumentos proporcionados por el programa find
:
$ find /usr/share/icons -name 'debian*' | xargs identify -format "%f: %wx%h\n" debian-swirl.svg: 48x48 debian-swirl.png: 22x22 debian-swirl.png: 32x32 debian-swirl.png: 256x256 debian-swirl.png: 48x48 debian-swirl.png: 16x16 debian-swirl.png: 24x24 debian-swirl.svg: 48x48
El programa identify
es parte de ImageMagick, un conjunto de herramientas de línea de comandos para inspeccionar, convertir y editar la mayoría de los tipos de archivos de imagen. En el ejemplo, xargs
tomó todas las rutas listadas por find
y las puso como argumentos para identify
, que luego muestra la información para cada archivo formateado como lo requiere la opción -format
. Los archivos encontrados por find
en el ejemplo son imágenes que contienen el logotipo de distribución en un sistema de archivos Debian. -format
es un parámetro para identify
, no para xargs
.
La opción -n 1
requiere que xargs
ejecute el comando dado con un solo argumento a la vez. En el caso del ejemplo, en lugar de pasar todas las rutas encontradas por find
como una lista de argumentos para identify
, usar xargs -n 1
ejecutaría el comando identify
para cada ruta por separado. El uso de -n 2
ejecutaría identify
con dos rutas como argumentos, -n 3
con tres rutas como argumentos y así sucesivamente. De manera similar, cuando xargs
procesa contenidos de varias líneas, como es el caso de la entrada proporcionada por find
, la opción -L
puede usarse para limitar cuántas líneas se usarán como argumentos por ejecución de comando.
Note
|
Usar |
Si las rutas tienen caracteres de espacio, es importante ejecutar find
con la opción -print0
. Esta opción indica a find
que use un carácter nulo entre cada entrada para que la lista pueda ser analizada correctamente por xargs
(se suprimió la salida):
$ find . -name '*avi' -print0 -o -name '*mp4' -print0 -o -name '*mkv' -print0 | xargs -0 du | sort -n
La opción -0
le dice a xargs
que el caracter nulo debe usarse como separador. De esa manera, las rutas de archivo proporcionadas por find
se analizan correctamente incluso si tienen caracteres en blanco u otros caracteres especiales. El ejemplo anterior muestra cómo usar el comando du
para averiguar el espacio ocupado en disco de cada archivo encontrado y luego ordenar los resultados por tamaño. La salida fue suprimida por concisión. Tenga en cuenta que para cada criterio de búsqueda es necesario colocar la opción -print0
para find
.
Por defecto, xargs
coloca los argumentos del comando ejecutado en último lugar. Para cambiar ese comportamiento, se debe usar la opción -I
:
$ find . -mindepth 2 -name '*avi' -print0 -o -name '*mp4' -print0 -o -name '*mkv' -print0 | xargs -0 -I PATH mv PATH ./
En el último ejemplo, cada archivo encontrado por find
se mueve al directorio actual. Como las rutas de origen deben ser informadas a mv
antes de la ruta de destino, se da un término de sustitución a la opción -I
de xargs
que luego se coloca apropiadamente junto a mv
. Al utilizar el caracter nulo como separador, no es necesario encerrar el término de sustitución con comillas.
Ejercicios Guiados
-
Es conveniente guardar la fecha de ejecución de las acciones realizadas por scripts automatizados. El comando
date +%Y-%m-%d
muestra la fecha actual en formato año-mes-día. ¿Cómo se puede almacenar la salida de dicho comando en una variable de shell llamadaTODAY
usando la sustitución de comandos? -
Usando el comando
echo
, ¿cómo se puede enviar el contenido de la variableTODAY
a la entrada estándar del comandosed s/-/./g
? -
¿Cómo podría usarse la salida del comando
date +%Y-%m-%d
como una cadena Here para ordenarsed s/-/./g
? -
El comando
convert image.jpeg -resize 25% small/image.jpeg
crea una versión más pequeña deimage.jpeg
y coloca la imagen resultante en un archivo con el mismo nombre dentro del subdirectoriosmall
. Usandoxargs
, ¿cómo es posible ejecutar el mismo comando para cada imagen listada en el archivofilelist.txt
?
Ejercicios Exploratorios
-
Una rutina de copia de seguridad simple crea periódicamente una imagen de partición
/dev/sda1
condd < /dev/sda1 > sda1.img
. Para realizar futuras comprobaciones de integridad de datos, la rutina también genera un hash SHA1 del archivo consha1sum < sda1.img > sda1.sha1
. Al agregar tuberías y el comandotee
, ¿cómo se combinarían estos dos comandos en uno? -
El comando
tar
se usa para archivar muchos archivos en uno solo, preservando la estructura del directorio. La opción-T
permite especificar un archivo que contiene las rutas a archivar. Por ejemplo,find /etc -type f | tar -cJ -f /srv/backup/etc.tar.xz -T -
crea un archivo tar comprimidoetc.tar.xz
de la lista provista por el comandofind
(la opción-T-
indica la entrada estándar como la lista de ruta). Para evitar posibles errores de análisis debido a las rutas que contienen espacios, ¿qué opciones de comando deberían estar presentes parafind
ytar
? -
En lugar de abrir una nueva sesión de shell remota, el comando
ssh
solo puede ejecutar un comando indicado como argumento:ssh user@storage "remote command"
. Dado quessh
también permite redirigir la salida estándar de un programa local a la entrada estándar del programa remoto, ¿cómo canalizaría el comandocat
un archivo local llamadoetc.tar.gz
a/srv/backup/etc.tar.gz
enuser@storage
a través dessh
?
Resumen
Esta lección cubre las técnicas tradicionales de comunicación entre procesos empleadas por Linux. La canalización de comandos crea un canal de comunicación unidireccional entre dos procesos y la sustitución de comandos permite almacenar la salida de un proceso en una variable de shell. La lección sigue los siguientes pasos:
-
Cómo se pueden usar pipes para transmitir la salida de un proceso a la entrada de otro.
-
El propósito de los comandos
tee
yxargs
. -
Cómo capturar el resultado de un proceso con command sustitution, almacenándolo en una variable o usándolo directamente como parámetro para otro comando.
Los comandos y procedimientos abordados fueron:
-
Comando de canalización con
|
. -
Sustitución de comandos con backticks y
$()
. -
Comandos
tee
,xargs
yfind
.
Respuestas a los ejercicios guiados
-
Es conveniente guardar la fecha de ejecución de las acciones realizadas por scripts automatizados. El comando
date +%Y-%m-%d
muestra la fecha actual en formato año-mes-día. ¿Cómo se puede almacenar la salida de dicho comando en una variable de shell llamadaTODAY
usando la sustitución de comandos?$ TODAY=`date +%Y-%m-%d`
o
$ TODAY=$(date +%Y-%m-%d)
-
Usando el comando
echo
, ¿cómo se puede enviar el contenido de la variableTODAY
a la entrada estándar del comandosed s/-/./g
?$ echo $TODAY | sed s/-/./g
-
¿Cómo podría usarse la salida del comando
date +%Y-%m-%d
como una cadena Here para ordenarsed s/-/./g
?$ sed s/-/./g <<< `date +%Y-%m-%d`
o
$ sed s/-/./g <<< $(date +%Y-%m-%d)
-
El comando
convert image.jpeg -resize 25% small/image.jpeg
crea una versión más pequeña deimage.jpeg
y coloca la imagen resultante en un archivo con el mismo nombre dentro del subdirectoriosmall
. Usandoxargs
, ¿cómo es posible ejecutar el mismo comando para cada imagen listada en el archivo` filelist.txt`?$ xargs -I IMG convert IMG -resize 25% small/IMG < filelist.txt
o
$ cat filelist.txt | xargs -I IMG convert IMG -resize 25% small/IMG
Respuestas a ejercicios exploratorios
-
Una rutina de copia de seguridad simple crea periódicamente una imagen de partición
/dev/sda1
condd < /dev/sda1 > sda1.img
. Para realizar futuras comprobaciones de integridad de datos, la rutina también genera un hash SHA1 del archivo consha1sum < sda1.img > sda1.sha1
. Al agregar tuberías y el comandotee
, ¿cómo se combinarían estos dos comandos en uno?# dd < /dev/sda1 | tee sda1.img | sha1sum > sda1.sha1
-
El comando
tar
se usa para archivar muchos archivos en uno solo, preservando la estructura del directorio. La opción-T
permite especificar un archivo que contiene las rutas a archivar. Por ejemplo,find /etc -type f | tar -cJ -f /srv/backup/etc.tar.xz -T -
crea un archivo tar comprimidoetc.tar.xz
de la lista provista por el comandofind
(la opción-T-
indica la entrada estándar como la lista de ruta). Para evitar posibles errores de análisis debido a las rutas que contienen espacios, ¿qué opciones de comando deberían estar presentes parafind
ytar
?Opciones
-print0
y--null
:$ find /etc -type f -print0 | tar -cJ -f /srv/backup/etc.tar.xz --null -T -
-
En lugar de abrir una nueva sesión de shell remota, el comando
ssh
solo puede ejecutar un comando indicado como argumento:ssh user@storage "remote command"
. Dado quessh
también permite redirigir la salida estándar de un programa local a la entrada estándar del programa remoto, ¿cómo canalizaría el comandocat
un archivo local llamadoetc.tar.gz
a/srv/backup/etc.tar.gz
enuser@storage
a través dessh
?$ cat etc.tar.gz | ssh user@storage "cat > /srv/backup/etc.tar.gz"
o
$ ssh user@storage "cat > /srv/backup/etc.tar.gz" < etc.tar.gz