sitio personal de Rodrigo Garcia Saenz.

Charla jugando con redireccion en bash

permalink.

Actualizado - 23 July 2023

He propuesto dar esta charla para el Debian Day 2017 en La Paz y este art铆culo es para quienes no puedan asistir a la charla o quieren revisarla con m谩s calma.

Mostrar茅 las formas en las que he estado jugando con operadores de redireccionamineto y tuber铆as en bash como interprete de comandos en GNU/Linux. (descargar las diapositivas (155KB))


Brevemente sobre file-descriptors

Un programa o comando en GNU/Linux puede hacer muchas cosas y entre estas esta interactuar con otros programas o usuarios extrayendo informaci贸n de alg煤n lugar y tambi茅n poner los resultados de lo que hace en otro lugar.

T铆picamente el "lugar" desde donde los programas extraen informaci贸n es un stream al que se le llama la entrada est谩ndar o stdin (que viene de standard input). El lugar hacia donde reportan sus acciones es la salida est谩ndar o stdout y donde reportan errores se llama el error est谩ndar stderr. Los streams que utilizan los procesos son listados en file-descriptors asociados a cada proceso.

Estos tres streams son los m谩s utilizados y estandarizados pero en este sistema operativo se pueden usar otros mas y podr铆amos utilizarlos como nos convega.

Un descriptor de archivo o file-descriptor es como un indicador que dice los streams que usa un archivo. En sistemas UNIX todo es un archivo, y al estar GNU/Linux basado en UNIX, un proceso tienen una serie de archivos asociados a este que lo describen y ayudan a gestionarlo. (en este https://pastebin.com/raw/PnUj8UPt puedes ver otra descripci贸n sencilla sobre file-descriptors)

Por ejemplo podemos ver los archivos asociados un proceso, en este caso el proceso de la terminal que tenemos abierta con:

lsof -p $BASHPID

Donde lsof es un comando que se utiliza para ver los archivos abiertos y con -p $BASHPID filtramos solo los archivos del identificador de proceso (PID) de la misma terminal abierta, en bash la variable $BASHPID es la que alamcena el PID de la terminal abierta, para filtrar solamente los file-descriptors asociados al proceso de la terminal acutal podemos usar:

lsof +f g -ap $BASHPID -d 0,1,2

(Ejemplo extra铆do de http://wiki.bash-hackers.org/howto/redirection_tutorial)

El resultado muestra entre muchas cosas los file descriptros (FD) usados por el proceso con PID 6841:

COMMAND  PID   USER   FD   TYPE FILE-FLAG DEVICE SIZE/OFF NODE NAME
bash    6481 lorilu    0u   CHR        RW  136,5      0t0    8 /dev/pts/5
bash    6481 lorilu    1u   CHR        RW  136,5      0t0    8 /dev/pts/5
bash    6481 lorilu    2u   CHR        RW  136,5      0t0    8 /dev/pts/5

En el caso anterior con -d 0,1,2 filtramos los file descriptors 0,1,2 que se asocian a stdin, stdout y stderr respectivamente ya que:

         Stream                File-descriptor (FD)

 stdin (entrada estandar) --->      0 
 stdout (salida estandar) --->      1
 stderr (error estandar)  --->      2

Si queremos ver todos los file descriptors asociados a un proceso, podemos listar el contenido del directorio /proc/<PID>/fd, donde <PID> viene a ser el PID del proceso.

Los operadores de redirecci贸n entre los que est谩n > >> < n>m &> o las tuber铆as | ayudan a manipular la fuente o destino de informaci贸n con la que los programas interact煤an y fuer贸n programados para brindar un alto nivel de flexiblidad.


Sobre operadores de redirecci贸n

Como sabemos que los programas act煤an sobre streams definidos por file-descriptors ahora podemos utilizar los operadores de redirecci贸n, que est谩n brevemente descritos en http://www.catonmat.net/download/bash-redirections-cheat-sheet.pdf

>

  • comando > archivo

El operador > redirije el stdout y lo guarda en un archivo.

# guarda la salida estandar de cada comando en archivos nuevos
ls > lista.txt
ps -aux --forest > lista_procesos.txt

Al usar comando > archivo se debe tener en cuenta que si archivo ya existe su contenido sera reemplazado por la salida est谩ndar de comando.

>>

  • comando >> archivo

>> al igual que > redirije el stdout, pero a diferencia de este adiciona el contenido de stdout al archivo y no lo reemplaza.

&> y &>>

  • comando &> archivo
  • comando &>> archivo

Por defecto redirije la stdout y stderr al archivo, pero puede usarse tambien de otros file-descriptors que veremos mas adelante.

<

  • comando < archivo

Redirecciona el contenido del archivo a la stdin, si el comando lee la entrada estandar estara en realidad leyendo el contenido del archivo y lo usara como argumento.

|

  • comando1 | comando2

Redirije stdout del comando1 a stdin y si el comando2 la lee la usara como argumento.

n>

  • comando 2> archivo

Funciona igual que > solo que en este caso 2> redirije el file-descriptor 2 que es stderr al archivo, de hecho se puede usar cualquier file desriptor por ejemplo comando 11> archivo que usara el file-descriptor 11 y lo guardara en archivo. El comportamiento es el mismo con n>>.

n<

  • comando 1< archivo

Al igual que < redirije el contenido de archivo al file-descriptor 1.

n>&m

  • comando 2>&1

Este operador hace que lo que haya escrito en el file-descriptor 2 (stderr) vaya al file-descriptor 1 (stdout)

El orden importa en bash

Bash eval煤a los operadores de redireccionamiento de izquierda a derecha.

Por ejemplo si queremos guardar en un archivo el error y salida estandar de un comando se hace asi, por ejemplo para ls:

ls algo-que-no-existe > archivo 2>&1

Gr谩ficamente se puede ver por pasos como funciona esto:

Primero.- Al evaluar bash > archivo los file-descriptors quedan de esta forma:

    File descriptor (FD)     Archivo

           0  --------->     /dev/pts/5
           1  --------->     archivo
           2  --------->     /dev/pts/5

Hay que notar que /dev/pts/5 es la terminal y por ende lo esta muestra en la pantalla, se ve que la entrada estandar stdin y stderr apuntan a la terminal actual /dev/pts/5. En cambio stdout est谩 apuntando a archivo

Luego.- bash eval煤a 2>&1 que hara que los file-descriptors queden de esta forma:

    File descriptor (FD)     Archivo

           0  --------->     /dev/pts/5
           1  --------->     archivo
                  /
           2  ---

Se ve que 2>&1 hace que lo que esta en stderr (file descriptor 2) se duplique en 1 (se copie en 1), y antes el file descriptor 1 estaba apuntando a archivo. De esta forma tanto stderr y stdout se guardaran en archivo.

Se puede ver adem谩s que bash ejecuta primero los operadores de redirecci贸n antes que los comandos.

Otro ejemplo si queremos ver con el comando less el error estandar y la salida estandar de un comando podemos usar lo siguiente:

  • comando1 2>&1 | less

Esto hace que el stderr vaya hacia stdout y como | redirije stdout al stdin del comando que esta a la derecha, less recibira los dos streams como argumentos.


Ejemplos de utilidad

En lo que queda de este tutorial se usar谩n ejemplos concretos para algunos operadores de redirecci贸n.

-

# obtener los 10 procesos que usan mas memoria RAM de todos los usuarios
ps aux --sort -rss | head

# obtener los 10 procesos que usan mas memoria RAM del usuario 'lorilu'
ps aux --sort -rss | head | grep -E "^lorilu"

En estos dos primeros ejemplos usamos ps --sort -rss para que ordene los procesos de todos los usuarios por el campo RSS que se refiere a la memoria residente o la cantidad de memoria RAM que ocupa acutalmente un proceso, usamos | para redirigir la stdout de ps a la stdin de head que muestra las 10 primeras l铆neas. Al agregar | grep -E "^lorilu" hacemos que grep reciba la salida de head y filtre solamente las l铆neas que empiezan en "lorilu" que es el nombre del usuario de quien buscamos los procesos abiertos, se busca solo en el principio de la l铆nea por que ps pone el nombre del usuario al principio de cada l铆nea.

-

# itera linea por linea en archivo.txt
while read -r linea 
do
   echo "$linea"
   # extas ...
done < archivo.txt

Este script muestra que bash ejecuta primero los operadores de redirecci贸n (en este caso <) antes que los comandos. En la parte final del script bash, < archivo.txt hace que stdin apunte al contenido de archivo.txt y esto hace que el bucle while itere sobre el contenido del archivo y haga que read -r lea cada linea y guarde el resultado de cada iteraci贸n en la variable linea, echo simplmente muestra la l铆nea actual. Dentro el blucle se pueden poner una cantidad indefinida de comandos para procesar cada l铆nea a conveniencia.

-

# renombra los archivos cuyos nombres tienen espacios en blanco y los reemplaza por _
for nombre in *
do
    nombre_sin_espacios=$(echo $nombre | tr " " "_")
    mv "$nombre" "$nombre_sin_espacios" 2> /dev/null
done

A veces es problem谩tico tener archivos con nombres con espacios en blanco como "archivo numero 1" lo que hace el script anterior es renombrarlo como "archivo_numero_1".

El bucle for actua iterando en * que coincide con cualquier archivo dentro el directorio actual y guardando cada nombre de archivo en nombre en cada iteraci贸n. Luego echo $nombre | tr " " "_" hace que tr reemplaze cualquier caracter " " por "_" y la salida de esto se guarda en la variable nombre_sin_espacios.

Luego se usa mv "$nombre" "$nombre_sin_espacios" para renombrar el archivo por su "equivalente" sin espacios en blanco.

La parte 2> /dev/null es opcional ya que para el caso en que los archivos no tengan nombres con espacios en blanco, mv mostrar谩 un error ya que los archivos son del mismo nombre y no pueden renombrarse, ese error va hacia stderr y se mostrar铆a en pantalla si no fuese por 2> /dev/null que pone stderr en /dev/null haciendo que se pierda y no se muestre en pantalla los errores producidos por mv, esto es es como silenciar los errores.

-

# Cifra una carpeta con la clave "123456" comprimiendola primero
tar -cf - carpeta/ | gpg --symmetric --no-use-agent --passphrase "123456" > carpeta.gpg

El comando tar puede comprimir archivos o carpetas pero requiere escribir el resultado en algun lugar por ejemplo tar -cf carpeta.tar.gz carpeta/ lo guarda en el archivo carpeta.tar.gz. En este caso para enviar su salida directamente a otro programa se usa tar - donde - es una opci贸n de tar para enviar el resultado a stdout (salida est谩ndar).

Luego a medida que la carpeta se va comprimiendo, mediante | se env铆a el resultado a gpg --symmetric que cifrar谩 lo que lea de stdin y con > carpeta.gpg se guarda la salida est谩ndard (stdout) en el archivo carpeta.gpg. Las opciones -no-use-agent --passphrase "123456" son para indicarle a gpg que no le pida al usuario ingresar la clave mediante teclado y en lugar de eso utilice "123456" para cifrar el mensaje.

-

# Enviar una carpeta al servidor algo.com a medida que es comprimida
# primero se requiere que en el servidor algo.com se tenga un puerto escuchando
# el trafico en un puerto dado para recibir el contenido enviado

# En el servidor
nc -l -p 5151 | tar -C . -xvf -

Primero en el servidor "algo.com" abrimos el puerto 5151 haciendo que nc (netcat) escuche el tr谩fico en este puerto. nc enviara a stdout el tr谩fico que reciba satisfactoriamente y con | hacemos que tar -xvf - descomprima lo que reciba en stdin y lo guarde en el directorio actual con las opciones -C .

# En el cliente (la maquina que envia la carpeta)
tar -cf - carpeta/ | nc -q 0 algo.com 5151

Como el servidor esta escuchando el tr谩fico en el puerto 5151, usamos tar -cf - carpeta/ para comprimir la carpeta y enviar el resultado a stdout. Con | lo redirijimos al stdin de nc algo.com 5151 que env铆a lo que lea del stdin al servidor algo.com por el puerto 5151. La opci贸n -q 0 de netcat hace que cuando se detecte EOF (fin de archivo) de la entrada est谩ndard al cabo de 0 segundos se cierre el programa.

De esta forma se puede enviar un archivo mediante TCP a medida que es comprimido y al llegar al servidor se ira descomprimiendo, se puede obviamente combinar esto con otros comandos para por ejemplo cifrar el contenido o analizar el env铆o en tiempo real.

-

# convertir una secuencia de imagenes .jpg en un archivo pdf ordenando alfabeticamente
ls *.jpg | sort -n | tr '\n' ' ' | sed 's/$/\ doc.pdf/' | xargs convert - doc.pdf

Soluci贸n extra铆da de este hilo en stack-overflow

Con ls *.jpg listamos todos los archivos de imagen .jpg, luego sort -n los ordena alfab茅ticamente, la lista ordenada la recibe tr '\n' ' ' que reemplaza los saltos de l铆nea \n por espacios en blanco , despu茅s sed 's/$/\ mydoc.pdf/' agrega al final de la l铆nea un espacio en blanco " " y la cadena "doc.pdf" esto por que el comando convert requiere recibir las imagenes seguido del nombre del archivo pdf que crear谩 a partir de los archivos de im谩genes que le preceden.

Finalmente mediante | xargs convert - doc.pdf se hacen varias cosas, primero el comando xargs se utiliza para pasarle como lista de argumentos la salida stdout de lo que esta a la izquierda de | al comando que esta a la derecha, en este caso es convert - doc.pdf que tomar谩 los nombres de los archivos de imagen y a partir de estos crear谩 el archivo doc.pdf.

Esto es 煤til cuando se requiren hacer reportes r谩pidos en pdf de una lista larga de im谩genes.

--> Espero este peque帽o compendio de utilidades te haya servido y tratar茅 de reunir m谩s pronto. Si tienes sugerencias u observaciones ponte en contacto

Lectura adicional

Consejo del d铆a

Trata de comprar art铆culos de tiendas o puestos de mercado familiares, esto por que cuando lo haces apoyas directamente a familias o personas individuales que luchan por subsistir.

En cambio cuando compras de supermercados o cadenas de tiendas grandes, est谩s dando ganacias adicionales a empresarios o empresarias grandes principalmente.