Hemos visto cómo escribir comandos simples, en los que un comando va seguido de sus argumentos. Ahora veremos cómo crear comandos más complejos componiendo comandos simples.
Antes de empezar, una breve introducción a la entrada/salida.
Los procesos tienen «canales de E/S estándar».
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 «stdio» más adelante en el temario.
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 obtener 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.
Por defecto, cada comando de una tubería se ejecuta en un subshell independiente. (Un subshell es un proceso hijo que es una copia del shell que se está ejecutando en ese momento.)
Ejecutar tuberías tiene un coste 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 normalmente se pueden combinar en un único 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 tuvo éxito, el estado de salida de toda la tubería es cero.
Hay una opción del shell que puede cambiar esto;
si la opción «pipefail» está activada (con set -o pipefail), el estado de salida de la tubería será:
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 de modo que el siguiente comando se ejecute de forma condicional según el estado de los comandos anteriores.
Por ejemplo
A && B && C && D || E
Usa estos operadores lógicos con moderación. Pueden dar lugar rápidamente a código ilegible o a una lógica difícil de comprender.
Por ejemplo, puede que pienses que estos son iguales:
if A; then B; else C; fi
A && B || C
La diferencia está en lo siguiente: ¿cuándo se ejecuta C?
Aquí tienes un par de ejemplos en los que las listas de comandos pueden simplificar el código.
Supón que tienes un fichero de datos que contiene información sobre triángulos, y que un triángulo se representa mediante 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
Muchos lenguajes de programación tienen una forma de afirmació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"
Las listas de comandos largas se vuelven difíciles de leer enseguida. Usar saltos de línea con generosidad puede ayudar mucho.
Fíjate en este ejemplo, en el que se añade una palabra a un array si se cumplen dos condiciones.
[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")
Bash te permite añadir un salto de línea después de una tubería o de un operador lógico.
[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")
Sin embargo, es fácil pasar por alto el operador al final de la línea. Usar una continuación de línea te permite poner el operador al principio, lo que deja más claro que la lista continúa:
[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
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, con lo que continúa la línea actual con la 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