Pipes: especial tuberías en Linux para hacerte todo un «fontanero» profesional

Las pipes o tuberías son una de las maravillas del mundo Unix que también ha heredado Linux. Gracias a eso y a que todo es representado como un fichero (otro de los pilares básicos de *nix), se pueden realizar tareas bastante interesantes con herramientas del intérprete de comandos cotidianas.

En este artículo intentaré explicar qué son las tuberías, cómo funcionan realmente, para qué se usan, y algunos secretos que quizás no conocías acerca de ellas…

Introducción

pipe, tubería Linux

Es importante conocer los flujos de trabajo en entornos Unix y Linux:

  • Entrada estándar o stdin (descriptor=0), desde donde se lee información, y que se corresponde con el teclado para introducir información.
  • Salida estándar o stdout (descriptor=1), donde se escribe información, y que se corresponde con la consola donde se muestra la información de salida de los programas.
  • Salida estándar de error o stderr (descriptor=2), que es el mismo medio que la stdout, solo que para mostrar mensajes de error.

Gracias a las tuberías (|) los procesos se pueden comunicar mediante un «canal» tipo FIFO (First In, First Out). Es decir, varios procesos se conectan enlazando la salida de un proceso con la entrada del siguiente proceso, creando una serie de procesos encadenados. Una magnífica idea que introdujo Douglas McIlroy en AT&T UNIX para dotarlo de mayor versatilidad y practicidad.

Douglas observó que frecuentemente se necesitaba enviar la salida de un programa hacia la entrada de otro, y la solución para poderlo hacer de una forma simple era la introducción de estas tuberías.

Cómo introducir tuberías

Para poder insertar tuberías y enlazar procesos, tan solo tienes que insertar los siguientes caracteres entre las órdenes que invocan a estos procesos:

  • | para canalizar la salida estándar de un comando hacia la entrada estándar de otro. Se pueden encadenar varios procesos.
  • |& para canalizar la salida de error estándar de un proceso hacia la entrada de otro.

Un ejemplo simple sería el siguiente:

ls | lpr

Esto lo que hace es listar el contenido del directorio actual y lo envía hacia la cola de impresión.

tee y xargs

Las tuberías tiene dos grandes aliados para trabajar con ellas.

cat ejemplo.txt | grep "Palabra" | tee resultado.txt | wc -l

El anterior comando usa el concatenador para canalizar el contenido del fichero ejemplo.txt hacia la entrada de grep que filtrará solo las líneas que contengan «Palabra». Ésta filtración a su vez se canalizará hacia tee, que mostrará el resultado por la salida estándar y también lo almacenará en el fichero resultado.txt. Finalmente, se canaliza el resultado hacia la entrada de wc para que recuente el número de líneas.

Además de tee, también debes conocer xargs. En este caso lo que hace es coger la salida estándar de un proceso y que el siguiente proceso acepte la entrada estándar como argumentos. Por ejemplo:

find ./ -name “thumbs.db” | xargs rm

Con el anterior comando se pueden eliminar todos los ficheros tumbs.db. Pero en vez de tener que ir ejecutando un rm por cada ruta donde se encuentre (rm /ruta/thumb.db, rm /otra/ruta/thumb.db,…), se hará todo de una vez haciendo que todos los ficheros localizados por find se usen como argumentos para rm.

Limites en el tamaño de buffer del kernel para tuberías

El tamaño del buffer del kernel para almacenar los datos de la canalización de las tuberías es limitado. Esto hará que se bloquee hasta que se recupere el espacio. Puedes ver información de distintos sistemas en esta tabla:

Limit FreeBSD 12.0 NetBSD 9.0 OpenBSD 6.6 Linux 5.6.14
_PC_PIPE_BUF 512 512 512 4096
PIPE_BUF 512 512 512 4096
PIPE_SIZE (implementation detail) 16384 16384 16384 N/A
ioctl(FIONSPACE) N/A 16384 N/A N/A
write(2) + alarm(3) 65536 16384 16384 65536
write(2) + O_NONBLOCK 98303 16384 49023 65536
«big» pipe on atomic write N/A 65536 N/A N/A

Tuberías en C

También puedes emplear las tuberías o pipes en el código fuente de tu software escrito en lenguaje de programación C. Esta herramienta es muy poderosa, y permite hacer cosas muy interesantes en tus programas. Para poder usarlas, se puede emplear la syscall pipe() y la biblioteca unistd.h.

Además de pipe() para las tuberías anónimas, las tuberías con nombre también se pueden crear con la función mkfifo o con la llamada al sistema mknod. Puedes ver más detalles en los ficheros de código fuente de Linux: fs/pipe.c y fs/fifo.c.

Para crear una pipe en el código C, hay que pasar como parámetro una tabla de datos enteros. Si la llamada se efectúa con éxito, la tabla de enteros contendrá el descriptor de lectura (filedes[0]) y el de escritura (filedes[1]) de la tubería.

//Fichero de cabecera necesario
#include <unistd.h>
//Sintaxis para la pipe
int pipe (int filedes[2])

En caso de que falle, se pueden generar los siguientes errores:

  • EFAULT: la tabla pasada como parámetro no es válida.
  • EMFILE: se ha alcanzado el número máximo de ficheros abiertos para el proceso actual.
  • ENFILE: se ha alcanzado el número máximo de ficheros abiertos en el sistema.

Además, también debes saber que es posible usar funciones de alto nivel de C para operaciones de lectura y escritura de estas tuberías. Sin embargo, estas operaciones usarán memorias intermedias, por lo que el carácter escrito no está inmediatamente disponible en el otro extremo de la tubería, a menos que cada operación de escritura vaya seguida de una llamada a fflush ().

Los descriptores inutilizados se pueden cerrar por la llamada al sistema close(). Una vez se ha cerrado, el descriptor ya no permitirá acceder a la pipe. De modo predeterminado, la lectura en una tubería es bloqueadora. El proceso lector queda bloqueado hasta que lee la cantidad de datos precisa en la llamada read(). Por tanto, dos procesos comunicantes pueden adoptar un protocolo para evitar bloquearse mutuamente.

Por ejemplo, en el siguiente código fuente se crea la tubería usando la syscall, luego se escribe la tubería usando el descriptor 1. Después los datos son leídos usando el otro extremo de la tubería con el descriptor 0. Para leer y escribir el fichero se usan las funciones o llamadas de lectura y escritura citadas anteriormente:

// Ejemplo del uso de una tubería en C
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int n;
    int filedes[2];
    char buffer[1025];
    char *message = "Hola AT!";

    pipe(filedes);
    write(filedes[1], message, strlen(message));

    if ((n = read ( filedes[0], buffer, 1024 ) ) >= 0) {
        buffer[n] = 0;  //Termina la cadena
        printf("Leyendo %d bytes de la tubería: "%s"\n", n, buffer);
    }  
    else
        perror("lectura");
    exit(0);
}

Ahora puedes compilar y probar este código

Isaac

Apasionado de la computación y la tecnología en general. Siempre intentando desaprender para apreHender.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

A %d blogueros les gusta esto:

Si continuas utilizando este sitio aceptas el uso de cookies. más información

Los ajustes de cookies de esta web están configurados para "permitir cookies" y así ofrecerte la mejor experiencia de navegación posible. Si sigues utilizando esta web sin cambiar tus ajustes de cookies o haces clic en "Aceptar" estarás dando tu consentimiento a esto.

Cerrar