Tracks
/
Bash
Bash
/
Temario
/
Tuberías y listas de comandos
Tu

Tuberías y listas de comandos en Bash

{one: "1 ejercicio", many: "%{count} de ejercicios", other: "%{count} ejercicios"}

Acerca de Tuberías y listas de comandos

Ya vimos cómo escribir comandos simples, en los que a un comando le siguen sus argumentos. Ahora veremos cómo construir comandos más complejos combinando comandos simples.

Entrada y salida

Antes de empezar, una breve introducción a la entrada y la salida.

Los procesos tienen «canales de E/S estándar».

  • Un proceso puede consumir entrada en «stdin».
  • Un proceso puede emitir salida en «stdout».
  • Un proceso puede emitir salida de error en «stderr».

El comando tr es un ejemplo muy puro de esto. Lo único que hace es leer texto de su entrada, realizar transliteraciones de caracteres e imprimir el texto resultante en su salida.

Veremos más sobre cómo manipular los canales de «stdio» más adelante en el temario.

Tuberías

Esta es una de las «funcionalidades estrella» de la programación de shell. Las tuberías te permiten crear transformaciones sofisticadas sobre un flujo de texto.

Para producir una lista ordenada de usuarios:

cat /etc/passwd | cut -d : -f 1 | sort

El símbolo de tubería (|) conecta la salida de un comando con la entrada de otro. cut lee la salida de cat, y sort lee la salida de cut.

Advanced
  • Por defecto, cada comando de una tubería se ejecuta en una subshell aparte. (Una subshell es un proceso hijo que es una copia del shell que se está ejecutando.)

  • Ejecutar tuberías tiene un costo de rendimiento. Si te encuentras con tuberías largas de comandos similares, considera combinarlas en un solo comando. Por ejemplo, las tuberías que usan varias instancias de grep, cut, sed, awk y tr por lo general se pueden combinar en un solo comando awk para mayor eficiencia.

  • El estado de salida de una tubería es el estado de salida del último comando de la tubería. Eso significa que, si algún comando anterior falló pero el último comando tuvo éxito, el estado de salida de toda la tubería es cero.

    Hay una configuración del shell que puede cambiar esto; si la configuración «pipefail» está habilitada (con set -o pipefail), el estado de salida de la tubería será:

    • cero si todos los comandos tienen éxito; de lo contrario,
    • el último estado de salida distinto de cero de los comandos de la tubería.

Listas de comandos

Una lista de comandos es una secuencia de tuberías separadas por ; (o un salto de línea), && o ||.

  • A; B es una lista de comandos en la que B se ejecuta después de que A haya terminado.
  • A && B, donde B se ejecuta solo si A tiene éxito (sale con estado cero).
  • A || B, donde B se ejecuta solo si A falla (sale con un estado distinto de cero).

El estado de salida de una lista de comandos es el estado de salida del último comando que se ejecutó.

Los operadores && y || se pueden encadenar para que el siguiente comando se ejecute de forma condicional según el estado de los comandos anteriores. Por ejemplo

A && B && C && D || E
  • B se ejecuta si A tiene éxito,
  • C se ejecuta si A y B tienen éxito,
  • D se ejecuta si A, B y C tienen éxito,
  • E se ejecuta si cualquiera de A, B, C o D falla.
Caution

Usa estos operadores lógicos con moderación. Pueden llevar rápidamente a código ilegible o a una lógica difícil de comprender.

Por ejemplo, podrías pensar que estos son iguales:

if A; then B; else C; fi
A && B || C

La diferencia es: ¿cuándo se ejecuta C?

  • En el primer fragmento (el condicional if), C se ejecutará solo si A falla.
  • En el segundo fragmento, ¡C se ejecutará si A falla o si A tiene éxito pero B falla!

Usos de las listas de comandos

Aquí tienes un par de ejemplos en los que las listas de comandos pueden simplificar el código.

Leer bloques de líneas de un archivo

Supón que tienes un archivo de datos con información sobre triángulos, y que cada triángulo se representa con tres líneas separadas que contienen los lados del triángulo.

$ cat triangle.dat
3
4
5
9
12
14

Puedes usar un bucle while en el que la condición sean tres comandos read separados:

while read a && read b && read c; do
    if is_pythagorean "$a" "$b" "$c"; then
        echo "$a:$b:$c is pythagorean"
    else
        echo "$a:$b:$c is not pythagorean"
    fi
done < triangle.dat

Suponiendo que is_pythagorean es un comando que determina si los tres lados satisfacen la ecuación de Pitágoras, la salida sería:

3:4:5 is pythagorean
9:12:14 is not pythagorean
Aserciones

Muchos lenguajes de programación tienen una forma de aserción en la que se lanza una excepción si alguna condición falla

assert(user == "Administrator", "wrong user");

Podemos usar un operador OR en Bash para simular esa función:

die () { # a little convenience function
    echo "$*" >&2
    exit 1
}

[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]]   || die "incorrect password"

Consideraciones de estilo

Las listas de comandos largas se vuelven difíciles de leer muy rápido. Usar saltos de línea con generosidad puede ayudar mucho.

Considera este ejemplo, en el que se agrega una palabra a un array si se cumplen dos condiciones.

[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")

Bash te permite agregar un salto de línea después de una tubería o un operador lógico.

[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")

Sin embargo, el operador puede pasar desapercibido fácilmente al final de la línea. Usar una continuación de línea te permite poner el operador al principio, lo que hace más obvio que la lista continúa:

[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
Note

Una continuación de línea es la secuencia de dos caracteres «barra invertida» y «salto de línea» (\ + \n). Cuando Bash ve esa secuencia, simplemente la elimina del código, y así continúa la línea actual con la línea siguiente. Ten cuidado de no dejar ningún espacio entre la barra invertida y el salto de línea.

Aquí tienes otro ejemplo

printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort

o

printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort
Editar en GitHub El enlace se abre en una ventana o pestaña nueva