2.6.1. Redirección básica¶
Para este caso trataremos sólo los tres archivos antes indicados:
Nombre |
Dipositivo |
Descriptor asociado |
---|---|---|
Entrada estándar |
|
0 |
Salida estándar |
|
1 |
Salida de errores |
|
2 |
y no haremos redirecciones permanentes.
La tercera columna, intitulada Descriptor asociado, hace referencia al descriptor de archivo, o sea, al número entero, con el que es posible hacer referencia al archivo en cuestión.
2.6.1.1. Salida¶
La redirección de salida consiste en redirigir la salida estándar o la salida de errores hacia otro archivo que puede ser la otra salida o un archivo regular.
Consideremos la siguiente orden:
$ cat
Sabemos ya que el comando leerá de teclado (la entrada estándar) y escribirá en la pantalla (la salida estándar). Si queremos redirigir la salida hacia un archivo regular, basta con lo siguiente:
$ cat > contenedor_de_tonterias
Esto que escribo, ya no aparece por la pantalla,
puesto que se redirige la salida estándar
hacia un archivo llamado 'contenedor_de_tonterias'.
Tal archivo puede o no existir: si no existe, se creará; si existe, se sustituirá su anterior contenido por lo que escribamos ahora. Es posible también redirigir al archivo añadiendo en vez de sustituyendo:
$ cat >> contenedor_de_tonterías
Añadimos un par de líneas adicionales
a las que escribimos antes
El resultado es que el archivo contendrá 5 líneas: ls tres anteriores y estas dos nuevas, en vez de contener sólo estas dos últimas, lo cual habría ocurrido si hubiéramos usado una redirección simple.
Hay ocasiones en que redirigimos la salida no porque deseemos guardar
resultados, sino porque deseamos no verlos. En este caso, es sumamente útil
el archivo especial /dev/null
, que se traga todo lo que le echemos sin
dejar rastro:
$ ls /usr/bin/ > /dev/null
Ahora bien, ¿por qué hemos redirigido la salida estándar y no la salida de errores? La razón está en que cuando no se indica cuál, se sobreentiende que nos referimos al descriptor 1, esto es, a la salida estándar. La sintaxis general de la redirección es:
N> destino
donde N
es el número del descriptor. La ausencia de N
implica 1. Para la
redirección doble es exactamente igual.
Probemos ahora lo siguiente:
$ sadhgaskjhsa > /dev/null
sadhgaskjhsa: no se encontró la orden
Como vemos, a pesar de la redirección, hemos visto el mensaje. Esto es debido a que lo que hemos redirigido es la salida estándar, no la salida de errores, por lo que esta última sigue saliendo por la pantalla. Para haber evitado ver el mensaje de error deberíamos haber hecho:
$ sadhgaskjhsa 2> /dev/null
También es posible redirigir una archivo de salida hacia el otro. Por ejemplo, esto redirige la salida de errores hacia la salida estándar:
$ sadhgaskjhsa 2>&1
sadhgaskjhsa: no se encontró la orden
Volvemos a ver por pantalla el error, pero en esta ocasión se escribe el mensaje en la salida de errores no en la estándar. De hecho:
$ sadhgaskjhsa >contenedor_del_error 2>&1
Escribe el mensaje de error en el archivo. En realidad, se redirigen tanto la salida estándar como la de errores[1]. No obstante, para esto último, es más sencillo redirigir ambas salidas a la vez, para lo cual hay un símbolo propio:
$ ls /y* &> /dev/listado.txt
Téngase en cuenta que esta última notación es propia de bash y no funciona en dash.
2.6.1.2. Entrada¶
Por su parte, redirigir la entrada consiste en alimentar con una fuente alternativa a un programa que espera recibir datos desde la entrada estándar, que en un principio es el teclado. El caso más sencillo es:
$ cat < archivo
[ ...Se muestra el contenido del archivo... ]
Como cat no tiene argumentos espera recibir datos a través de la entrada estándar; pero, como con secuencia de la redirección, esta pasa de ser el teclado a ser el archivo. Consecuentemente, lo que muestra cat es el contenido del archivo. En realidad, esto es equivalente a:
$ cat 0< archivo
Ya que 0 es el descriptor que representa la entrada estándar. Esta es la base de la redirección de entrada.
Otra redirección de entrada que también forma parte del estándar es la llamada Here Document que permite redirigir hacia la entrada estándar un texto largo de varias líneas. Para ello se define una palabra delimitadora, de manera que cuando se vuelva a encontrar esta misma palabra delimitadora sola al principio de línea, se considerará acabado el texto. Por ejemplo, si hacemos que nuestro delimitador sea EOF:
$ cat <<EOF
> En un país multicolor
> había una abeja bajo el sol
> EOF
En un país multicolor
había una abeja bajo el sol.
En el texto, la shell intentará llevar a cabo sustituciones por lo que:
$ cat <<EOF
> 4 * 2 = $((4*2))
> EOF
4 * 2 = 8
Ahora bien, si se rodea el delimitador de inicio con comillas dobles o simples, no se interpretará nada:
$ cat <<"EOF"
> 4 * 2 = $((4*2))
> EOF
4 * 2 = $((4*2))
Es posible anteponer al primer delimitador un guión para que pueda sangrarse (exclusivamente con tabulaciones) el texto del documento en línea que se escribe. Es útil cuando se programa y se quiere mantener el código correctamente sangrado:
$ cat <<-EOF
Mi hogar es $HOME
EOF
Mi hogar es /home/usuario
Here String
bash, además, ofrece esta redirección adicional, que permite redirigir hacia la entrada estándar una cadena:
$ cat <<<Hola,\ don\ Pepito.
Hola, don Pepito.
Nota
Obsérvese que Here String no es más que el caso particular de un Here Document de una sola línea:
$ cat <<EOF
> Hola, don Pepito.
> EOF
Hola, don Pepito
por lo que si al escribir un script en que deseamos evitar las extensiones de bash tenemos necesidad de usar un Here String[2], podemos usar un Here Document.
2.6.1.3. Tuberías¶
Las tuberías (pipelines en inglés) son un caso particular de una redirección
de salida junto a una redirección de entrada. Para entender su utilidad
supongamos que, con las herramientas vistas, se nos propone mostrar únicamente
la penúltima línea de /etc/group
.
Echando mano de la memoria, parece útil tail, capaz de extraer la parte final de un documento. En concreto:
$ tail -n2 /etc/group
libvirt-qemu:x:116:libvirt-qemu
qemusers:x:117:josem
muestra las dos últimas líneas. Pero resulta que sólo queremos la penúltima, o lo que es lo mismo, la primera línea de la salida producida por tail. Pero resulta que head permite extraer los comienzos de archivo, de modo que si aplicamos un head -n1 a esa salida conseguiremos nuestro objetivo. Por supuesto es posible hacer:
$ tail -n2 /etc/group > /tmp/archivo.intermedio
$ head -n1 < /tmp/archivo.intermedio
libvirt-qemu:x:116:libvirt-qemu
$ rm -f /tmp/archivo.intermedio
Pero nos obliga a crear un absurdo archivo intermedio que hay que borrar al
terminar. La solución fetén a nuestro problema son las tuberías (|
) que
permite redirigir la salida estándar de un programa hacia la entrada estándar
del siguiente:
$ tail -n2 /etc/group | head -n1
libvirt-qemu:x:116:libvirt-qemu
Esta es básicamente la idea de las tuberías: sencilla, pero que abre muchísimas posibilidades al permitir construir una herramienta más compleja mediante la cooperación de herramientas más simples.
La tubería, así escrita, sólo redirige la salida estándar. Si se quieren redirigir tanto la salida estándar como la de errores puede hacerse:
$ ls /g* 2>&1 | tail -n2
O bien, usar una sintaxis que sólo es admitida por bash:
$ ls /g* |& tail -n2
En lo relativo a redirecciones son muy útiles dos órdenes que las usan:
- tee
Desdobla su entrada hacia dos salidas: la estándar y el archivo que se indique:
$ ls / | tee /tmp/listado.txt
Hecho esto, veremos que el listado aparece en la pantalla, pero también se habrá almacenado en
/tmp/listado.txt
.
- pv
Este comando, simplemente, cuenta los bytes que recibe por la entrada estándar y los redirige hacia la salida estándar. Es bastante útil cuando el flujo de datos es grande y no sabemos muy bien cuándo acabará. Por ejemplo, supongamos que tenemos en el archivo
disco.img.xz
la imagen cruda comprimida de un disco y queremos volcarla sobre el disco físico/dev/sdb
. La solución es trivial:$ xzcat disco.img.xz > /dev/sdb
Ahora bien, la descompresión es un proceso lento y la escritura de tantos datos en el disco, también. Como consecuencia, no sabemos muy bien ni cuánto tardará ni la velocidad ia la que se van escribiendo datos, con lo que nos resulta imposible hacernos una idea de cómo va el proceso hasta que finalmente acaba. La solución es usar pv[3] como intermediario:
$ xzcat disco.img.xz | pv > /dev/sdb
De este modo, el proceso de volcado se llevará a cabo igualemente, ya que pv no altera los bytes, pero mostrará información de a qué velocidad se lleva a cabo el proceso y cuántos bytes han pasado por el momento a través de él. No, puede, sin embargo, pronosticarnos cuánto tiempo tardará ni decirnos cuál es el porcentaje ya volcado porque ignora el tamaño final de aquello que se le pasa. No obstante, si nosotros sabemos cuál es el tamaño descomprimido de la imagen, porque recordamos de cuánto era el disco del que la hicimos, entonces es posible indicarle a pv cuál es la cantidad total de bytes que pasará a través de él (
-s
) y pedirle que nos muestre una barra de progreso con el porcentaje completado (-p
):$ xzcat disco.img.xz | pv -ps 250G > /dev/sdb
No obstante, para este caso particular, pv permite también indicar en sus argumentos un archivo del que leer, en vez de usar la entrada estándar. En este caso, pv si es capaz de saber cuántos bytes leerá, puesto que toma el dato del tamaño del archivo, y esto hace que sea innecesario pasar con
-s
la cantidad. Así pues, lo anterior, habría sido más inteligente haberlo hecho del siguiente modo:$ pv -p disco.img.xz | xzcat > /dev/sdb
Nota
Nótese que en este último caso la cantidad de bytes que pasan por pv es significativamente menor, ya que no pasa la imagen descomprida, sino la comprimida. Por tanto, el total no serán 250GB sino solamente quizás 5GB, por decir algo. Sin embargo, como los tres comandos tienen que sincronizarse puesto que unos alimentan a otros, el dato del tiempo restante y el porcentaje completado es absolutamente verídico. De hecho, no están sujetos a la arbitrariedad de nuestra memoria ni que a que, posiblemente, el tamaño de disco no sean exactamente 250GB[4].
Por último, debe tener presente que para que se pueda usar una tubería la salida de una orden (la «A») sea la entrada de la siguiente («B»):
$ ordenA | ordenB
exige que la ordenA sea capaz de escribir su resultado en la salida estándar y la ordenB sea capaz de leer de la entrada estándar. Sin embargo, puede ocurrir que la sintaxis de la ordenA sólo nos permita escribir su resultado en un archivo, no en la salida estándar, o que la sintaxis de la ordenB sólo nos permita leer de un archivo y no de la entrada estándar. En esas condiciones nos es imposible utilizar la tubería y tendremos que:
Utilizar el archivo intermedio, que es la estrategia más grosera. Por ejemplo, si la ordenB sólo puede leer de un archivo que se le pasa como argumento:
$ ordenA > /tmp/entrada.txt $ ordenB /tmp/entrada.txt $ rm /tmp/entrada.txt
La estrategia funciona pero supone que esperemos a que la ordenA acabe de escribir el resultado y, además, que el resultado tengamos que almacenarlo temporalmente en disco.
Utilizar tuberías con nombre.
Usar la sintaxis que brinda bash llamada en su manual process substitution.
- Tuberias con nombre
Las tuberías con nombre consisten en crear un archivo especial que representa una tubería con la orden mkfifo y hacer que los dos programas involucrados lean y escriban en él, como si se tratara de un archivo regular. La ventaja es que la transferencia de datos se lleva a cabo del mismo modo que cuando se usan tuberías anónimas normales y por tanto, ambos procesos sincronizan la producción y el consumo, por lo que no se almacenan datos en disco. De este modo, si es la ordenB la que tiene que leer de archivo, podemos hacer:
$ mkfifo /tmp/tuberia $ ordenA > /tmp/tuberia & ordenB /tmp/tuberia $ rmdir /tmp/tuberia
y si es la ordenA la que solo puede escribir en un archivo:
$ mkfifo /tmp/tuberia $ ordenA /tmp/tuberia & ordenB </tmp/tuberia
- Process substitution (extensión de bash, incompatible con POSIX)
El primer caso de limitación es que ordenB sólo sea capaz de leer de archivo, esto es, la sintaxis posible es ordenB archivo-entrada. Si es así, la forma imposible:
$ ordenA | ordenB
se puede resolver con:
$ ordenB <(ordenA)
mientras que el segundo caso de limitación, que consiste en que ordenA sólo es capaz de escribir su resultado en un archivo, esto es, que la sintaxis posibles es ordenA archivo-salida, como no puede resolverse:
$ ordenA | ordenB
se resuelve:
$ ordenA >(ordenB)
Nota
Con POSIX puede subsanarse la carencia, aunque utilizando técnicas de redirección algo avanzada. Así la expresión:
$ ordenB <(ordenB)
puede lograrse de este modo:
$ ordenA | ordenB /dev/fd/0
o de forma más general usando otro descriptor:
$ ordenA 3<&- | ordenB /dev/fd/3 3<&0
Por su parte:
$ ordenA >(ordenB)
puede emularse con:
$ ordenA /dev/fd/1 | ordenB
o de forma más general usando otro descriptor:
$ ordenA /dev/fd/3 3>&1 | ordenB 3>&-
Para que no quede expresado de forma tan general este apartado supongamos que queremos en una misma orden guardar el contenido del directorio raíz y a la vez contar cuántos archivos contiene. La solución es la siguiente:
$ ls / | tee >(wc -l) >/tmp/listado.txt
Notas al pie