2.5.1. Redirección básica

Para este caso trataremos sólo los tres ficheros antes indicados:

Nombre

Dipositivo

Descriptor asociado

Entrada estándar

/dev/stdin

0

Salida estándar

/dev/stdout

1

Salida de errores

/dev/stderr

2

y no haremos redirecciones permanentes.

La tercera columna, intitulada Descriptor asociado, hace referencia al descriptor de fichero, o sea, al número entero, con el que es posible hacer referencia al fichero en cuestión.

2.5.1.1. Salida

La redirección de salida consiste en redirigir la salida estándar o la salida de errores hacia otro fichero que puede ser la otra salida o un fichero 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 fichero 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 fichero llamado 'contenedor_de_tonterias'.

Tal fichero 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 fichero 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 fichero 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 fichero 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 fichero 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 fichero. En realidad, se redirigen tanto la salida estándar como la de errores1. 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.5.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 < fichero
[ ...Se muestra el contenido del fichero... ]

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 fichero. Consecuentemente, lo que muestra cat es el contenido del fichero. En realidad, esto es equivalente a:

$ cat 0< fichero

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 String2, podemos usar un Here Document.

2.5.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 fichero, de modo que si aplicamos un head -n1 a esa salida conseguiremos nuestro objetivo. Por supuesto es posible hacer:

$ tail -n2 /etc/group > /tmp/fichero.intermedio
$ head -n1 < /tmp/fichero.intermedio
libvirt-qemu:x:116:libvirt-qemu
$ rm -f /tmp/fichero.intermedio

Pero nos obliga a crear un absurdo fichero 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 fichero 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 fichero 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 pv3 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 fichero 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 fichero, 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 250GB4.

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 fichero, no en la salida estándar, o que la sintaxis de la ordenB sólo nos permita leer de un fichero y no de la entrada estándar. En esas condiciones nos es imposible utilizar la tubería y tendremos que:

  • Utilizar el fichero intermedio, que es la estrategia más grosera. Por ejemplo, si la ordenB sólo puede leer de un fichero 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 fichero 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 fichero regular. La ventaja es que la transferencia de datos se lleva a cabo del mismo que cuando se usan tuberías anónimas nrmales y por tanto, ambos procesos se sincronizan la producción y el consumo, por lo que no se almacenan datos en disco. De estemodo si es la ordenB la que tiene que leer de fichero, 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 fichero:

$ 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 fichero, esto es, la sintaxis posible es ordenB fichero-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 fichero, esto es, que la sintaxis posibles es ordenA fichero-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 ficheros contiene. La solución es la siguiente:

$ ls / | tee >(wc -l) >/tmp/listado.txt

Notas al pie

1

¡Téngase cuidado! No vale cambiar de orden las redirecciones:

$ sadhgaskjhsa 2>&1 >contenedor_del_error
sadhgaskjhsa: no se encontró la orden

ya que en este caso se redirige la salida del descriptor 2 hacia la salida del descriptor 1, que en el momento de la redirección sigue siendo aún la pantalla.

2

EN principio, podemos usar una tubería para emular un Here String:

$ echo "Hola, don Pepito" | cat
Hola, don Pepito

pero en ese caso la parte derecha de la tubería se ejecuta dentro de una subshell, cuyo inconveniente principal es que impide definir o redefinir el valor de una variable, ya que lo que se haga en la subshell, en la subshell queda. Por ejemplo, si hiciéramos uso de read, que veremos a continuación:

$ echo "valor" | read -r VAR
$ echo $VAR

no habría conseguido dar valor a VAR, porque al salir de la subshell queda sin ehecto la asignación de valor.

3

No viene instalado de serie en el sistema, por lo que hay que instalar el paquete del mismo nombre.

4

Recordemos que los fabricantes de discos utilizan los múltiplos del bytes suponiendo que la relación entre ellos es 1000, cuando las unidades que maneja el sistema operativo siempre son en múltiplos de 1024.