GNU/Linux >> Tutoriales Linux >  >> Linux

Usando Bash para la automatización

Dada la serie reciente de artículos que cubren los aspectos fundamentales de Bash (enumerados al final de este artículo), es inevitable que uno de sus nuevos colegas arroje uno a la nube. A medida que avanzan estas cosas, los siguientes pasos lógicos son:

  1. Verificar algo absolutamente crítico depende de que el script de "nube" funcione sin problemas.
  2. Verifique que el autor original del guión haya olvidado por completo cómo funciona realmente.
  3. Verifique que el administrador más nuevo tenga la tarea de modificarlo fundamentalmente sin ningún tipo de validación.

En este artículo, ayudo a todos los administradores y los ayudo a evitar todos los errores anteriores. A su vez, el resultado conduce a una administración más feliz y, con suerte, a nuestro empleo continuo.

Cómo se escribe "script Bash"

Para iluminarse (y por su amor a $DEITY), verifique su código en una herramienta de administración de código fuente (SCM). Incluso mientras está aprendiendo, use un repositorio local como patio de recreo. Esta práctica no solo le permite llevar un registro de sus esfuerzos a lo largo del tiempo, sino que también le permite deshacer fácilmente los errores. Hay muchos artículos maravillosos sobre cómo empezar con git que recomiendo encarecidamente.

Una nota rápida sobre el uso y el aprendizaje de Bash, porque este lenguaje de secuencias de comandos trae un conjunto único de -ismos y preferencias estilísticas del autor:tan pronto como detecte algo que le parezca nuevo (sintaxis, estilo o una construcción del lenguaje), búsquelo de inmediato. . Dedique su tiempo a comprender el nuevo elemento de la página del manual (primera opción) o la Guía avanzada de secuencias de comandos de Bash (se puede acceder a ambos sin conexión). Hacer esto lo ralentizará al principio, pero con el tiempo esta práctica lo ayudará a desarrollar su conocimiento sobre dónde encontrar respuestas.

Escribir nuggets de Bash reutilizables como bibliotecas

Los scripts en automatización se escriben mejor si adoptan la filosofía de Unix:muchas herramientas pequeñas que solo hacen una cosa. Lo que significa que le irá mucho mejor escribiendo scripts y bibliotecas pequeñas y especializadas que con un "fregadero de cocina" gigante. Aunque, es cierto, he escrito y mantenido algunos gigantes (ocasionalmente tienen un propósito).

Los scripts de automatización con frecuencia deben ser comprensibles y mantenibles por más de un autor. Con muchos pequeños scripts circulando (y siendo rastreados en el control de versiones), rápidamente necesitará compartir una referencia a valores para nombres, versiones, rutas o URL. Escribir estos elementos comunes en bibliotecas también proporciona espacio mental adicional para que los mantenedores aprecien la documentación en línea. Además, hacerlo hace que el aprovechamiento de las pruebas unitarias sea casi trivial (abordaremos este tema al final).

Practiquemos una buena higiene del código desde el principio creando un repositorio local de "juego". Dentro de su nuevo repositorio, cree un lugar para contener nuestros scripts y archivos de biblioteca. Me gusta apegarme a los estándares FHS bien entendidos por simplicidad. Crea los directorios ./bin/ y ./lib/ en la raíz del repositorio. En un proyecto de automatización más grande, todavía usaría estos nombres, pero podrían estar profundamente enterrados (por ejemplo, bajo un scripts o tools subdirectorio).

Hablar de caminos me lleva a un gran tema para una primera biblioteca. Necesitamos una forma para que nuestros futuros componentes hagan referencia a elementos estructurales y valores de alto nivel. Usando su editor favorito, cree el archivo ./lib/anchors.sh y agregue el contenido a continuación:



# A Library of fundamental values
# Intended for use by other scripts, not to be executed directly.

# Set non-'false' by nearly every CI system in existence.
CI="${CI:-false}"  # true: _unlikely_ human-presence at the controls.
[[ $CI == "false" ]] || CI='true'  # Err on the side of automation

# Absolute realpath anchors for important directory tree roots.
LIB_PATH=$(realpath $(dirname "${BASH_SOURCE[0]}"))
REPO_PATH=$(realpath "$LIB_PATH/../")  # Specific to THIS repository
SCRIPT_PATH=$(realpath "$(dirname $0)")

El archivo comienza con dos líneas en blanco y el primer comentario explica por qué. La biblioteca debería no establecerse como ejecutable (a pesar de que el nombre termine en .sh, lo que indica su tipo). Si la biblioteca se ejecutó directamente, es posible que haga que el shell del usuario desaparezca (o algo peor). Deshabilitar la ejecución directa (chmod -x ./lib/anchors.sh ) es el primer nivel de protección para administradores principiantes. El comentario al principio del archivo es el segundo nivel.

Por convención, los comentarios siempre deben describir el por qué (no el qué ) de las afirmaciones que siguen. Un lector puede simplemente leer la declaración para entender lo que hace, pero no puede intuir de manera confiable lo que el autor estaba pensando en ese momento. Sin embargo, antes de profundizar, necesito detallar un problema que a menudo toma a las personas con la guardia baja con Bash.

Los valores predeterminados de Bash proporcionan una cadena vacía al hacer referencia a una variable no definida. El CI variable (o algo análogo en su automatización) pretende indicar la probable ausencia de un ser humano. Desafortunadamente, para los señores de los robots, los humanos probablemente necesitarán ejecutar el script manualmente al menos una vez. En este punto, es probable que se olviden de establecer un valor para CI antes de presionar Enter.

Por lo tanto, debemos establecer un valor predeterminado para el valor y asegurarnos de que siempre sea verdadero o falso. El código de la biblioteca de ejemplo anterior demuestra cómo probar si una cadena está vacía y cómo forzar la cadena para que contenga uno de un par de valores. La forma en que leo el primer grupo de declaraciones en anchors.sh es:

Definir 'CI ' en adelante como resultado de:

  1. Examinar el valor anterior de CI (puede no estar definido, por lo que es una cadena vacía).
  2. El ':- ' parte significa:
    1. Si el valor era una cadena vacía, represente la cadena 'false ' en su lugar.
    2. Si el valor no era una cadena vacía, use lo que fuera (incluido 'DaRtH VaDeR ').

Prueba lo que hay dentro de '[[ ' y ' ]] ':

  1. Si el nuevo valor de 'CI ' es igual a la cadena literal "false ", lanza el código de salida 0 (que significa éxito o verdad).
  2. De lo contrario, lanza el código de salida 1 (que significa fracaso o falsedad)

Si la prueba salió con 0 , continúe con la siguiente línea, o (el ' || ' parte), suponga que un administrador novato establece CI=YES!PLEASE o un conjunto de máquinas perfectas CI=true . Establezca inmediatamente el valor de 'CI ' a la cadena literal 'true '; porque maquinas perfectas es mas mejor, no se equivoquen.

Para el resto de esta biblioteca, los valores de la ruta de anclaje casi siempre son útiles en los scripts que se ejecutan en la automatización desde un repositorio. Cuando se usa en un proyecto más grande, deberá ajustar la ubicación relativa del directorio de la biblioteca con respecto a la raíz del repositorio. De lo contrario, dejaré la comprensión de estas afirmaciones, como un ejercicio de investigación para el lector (hazlo ahora, te ayudará más adelante).

Uso de bibliotecas Bash en scripts Bash

Para cargar una biblioteca, use el source comando incorporado. Este comando no es elegante. Dale la ubicación de un archivo, y luego lee y ejecuta el contenido allí mismo, lo que significa que el tiempo de ejecución del código de la biblioteca contexto en realidad será el script que source d eso.

Para ayudar a evitar que una gran parte de su cerebro gotee fuera de sus oídos, aquí hay un simple ./bin/example.sh guión para ilustrar:

#!/bin/bash

LIB_PATH="$PWD/$(dirname $0)/../lib/anchors.sh"
echo "Before loading: $LIB_PATH"
set -ax
cd /var/tmp
source $LIB_PATH
echo "After loading: $(export -p | grep ' LIB_PATH=')"

Es posible que note de inmediato que la secuencia de comandos alteró el contexto de tiempo de ejecución antes de cargar la biblioteca. También define LIB_PATH localmente y lo apunta a un archivo (confusamente, en lugar de un directorio) con una ruta relativa (para fines ilustrativos).

Continúe y ejecute este script y examine la salida. Observe que todas las operaciones en la biblioteca anchors.sh se ejecutó dentro de /var/tmp/ directorio y exportó automáticamente sus definiciones. La antigua definición de LIB_PATH fue golpeado y exportado por el a en el set -ax . Este hecho es visible en la salida de declare -x procedente de la export dominio. Con suerte, la salida de depuración (el x en el set -ax ) es comprensible.

Al depurar de esta manera, Bash imprime todos los valores intermedios al analizar cada línea. Incluí este script aquí para mostrar por qué nunca querrías set -ax o cambiar directorios usando los comandos del nivel superior de una biblioteca. Recuerde que las instrucciones de la biblioteca se evalúan en el momento de la carga en el script. Por lo tanto, cambiar el entorno en una biblioteca hace que haya efectos secundarios en el tiempo de ejecución en cualquier secuencia de comandos utilizada source para cargarlo Se garantiza que los efectos secundarios como este volverán completamente loco al menos a un administrador de sistemas. Nunca se sabe, ese administrador podría ser yo, así que no lo hagas.

Como ejemplo práctico, considere una biblioteca imaginaria que define una función utilizando una variable de entorno de nombre de usuario/contraseña para acceder a un servicio remoto. Si la biblioteca hiciera un set -ax de nivel superior antes de esta función, cada vez que se cargue, la salida de depuración incluirá la visualización de estas variables, salpicando sus secretos por todos lados para que todos los vean. Peor aún, será difícil (desde la perspectiva de un script de llamadas) para un colega principiante deshabilitar la salida sin gritarle al teclado.

En conclusión, la clave aquí es ser consciente de que las bibliotecas "suceden" dentro del contexto de la persona que llama. Este factor también es la razón por la cual el ejemplo anchors.sh puede usar  $0   (la ruta del script ejecutable y el nombre del archivo), pero la ruta a la biblioteca en sí solo está disponible a través del "código mágico" '${BASH_SOURCE[0]}' (elemento de matriz). Este factor puede ser confuso al principio, pero debes intentar ser disciplinado. Evite los comandos amplios y de gran alcance en las bibliotecas. Cuando lo haga, todos los nuevos administradores insistirán en pagar sus donas.

Escribir pruebas unitarias para bibliotecas

Escribir pruebas unitarias puede parecer una tarea desalentadora hasta que te das cuenta de que una cobertura perfecta suele ser una pérdida de tiempo. Sin embargo, es un buen hábito usar y actualizar siempre su código de prueba cuando toque su código de biblioteca. El objetivo de la redacción de pruebas es ocuparse de los casos de uso más comunes y obvios y luego seguir adelante. No prestes mucha atención a los casos de esquina o menos que los usos todo el tiempo. También sugiero centrar inicialmente sus esfuerzos de prueba de biblioteca en el nivel de prueba unitaria en lugar de la prueba de integración.

Veamos otro ejemplo:el script ejecutable ./lib/test-anchors.sh :

#!/bin/bash

# Unit-tests for library script in the current directory
# Also verifies test script is derived from library filename

TEST_FILENAME=$(basename $0)  # prefix-replace needs this in a variable
SUBJ_FILENAME="${TEST_FILENAME#test-}"; unset TEST_FILENAME
TEST_DIR=$(dirname $0)/

ANY_FAILED=0

# Print text after executing command, set ANY_FAILED non-zero on failure
# usage: test_cmd "description" <command> [arg...]

test_cmd() {
   local text="${1:-no test text given}"
   shift
   if ! "$@"; then
      echo "fail - $text"; ANY_FAILED=1;
   else
      echo "pass - $text"
   fi
}

test_paths() {
   source $TEST_DIR/$SUBJ_FILENAME
   test_cmd "Library $SUBJ_FILENAME is not executable" \
      test ! -x "$SCRIPT_PATH/$SUBJ_FILENAME"
   test_cmd "Unit-test and library in same directory" \
      test "$LIB_PATH" == "$SCRIPT_PATH"
   for path_var in LIB_PATH REPO_PATH SCRIPT_PATH; do
      test_cmd "\$$path_var is defined and non-empty: ${!path_var}" \
         test -n "${!path_var}"
      test_cmd "\$$path_var referrs to existing directory" \
         test -d "${!path_var}"
   done
}

# CI must only/always be either 'true' or 'false'.
# Usage: test_ci <initial value> <expected value>

test_ci() {
   local prev_CI="$CI"  # original value restored at the end
   CI="$1"
   source $TEST_DIR/$SUBJ_FILENAME
   test_cmd "Library $SUBJ_FILENAME loaded from $TEST_DIR" \
      test "$?" -eq 0
   test_cmd "\$CI='$1' becomes 'true' or 'false'" \
      test "$CI" = "true" -o "$CI" = "false"
   test_cmd "\$CI value '$2' was expected" \
      test "$CI" = "$2"
   CI="$prev_CI"
}

test_paths
test_ci "" "false"
test_ci "$RANDOM" "true"
test_ci "FoObAr" "true"
test_ci "false" "false"
test_ci "true" "true"

# Always run all tests and report, exit non-zero if any failed

test_cmd "All tests passed" \
   test "$ANY_FAILED" -eq 0
[[ "$CI" == "false" ]] || exit $ANY_FAILED  # useful to automation
exit(0)

La razón por la que puse este script en ./lib (a diferencia de ./bin ) es tanto por conveniencia como porque las pruebas nunca deben depender del uso del código que están comprobando. Debido a que esta prueba necesita verificar las rutas, es más fácil colocarla en la misma ruta que la biblioteca. De lo contrario, este enfoque es una cuestión de preferencia personal. Siéntase libre de ejecutar la prueba ahora porque podría ayudarlo a comprender el código.

Resumiendo

Este artículo de ninguna manera representa la totalidad del uso de Bash en la automatización. Sin embargo, traté de inculcarte conocimientos básicos y recomendaciones que (si las sigues) sin duda te harán la vida más fácil. Entonces, incluso cuando las cosas se vuelvan difíciles, será útil una comprensión firme de la importancia del contexto del tiempo de ejecución.

Finalmente, la creación de scripts en o para la automatización puede ser implacable con los errores. Tener incluso pruebas de unidades básicas implementadas para sus bibliotecas generará confianza y ayudará a la próxima persona que aparezca (que podría ser usted después de cinco años de olvido). Puede encontrar todo el código de ejemplo utilizado en este artículo en línea aquí.

¿Está interesado en repasar los fundamentos de Bash? Mira:

  • Cómo programar con Bash:operadores lógicos y expansiones de shell
  • 5 formas de mejorar sus scripts de Bash
  • Introducción a las secuencias de comandos de shell
  • 10 comandos básicos de Linux que debe conocer
  • Consejos y trucos de variables de entorno de Linux

[ ¿Quiere probar Red Hat Enterprise Linux? Descárgalo ahora gratis. ]


Linux
  1. Manejo de errores en scripts Bash

  2. Uso de la herramienta SS para la resolución de problemas de red

  3. ¿Usar la extensión .sh o .bash para scripts Bash?

  4. Usando el comando de suspensión de Linux en Bash Scripts

  5. automatizar la sesión de telnet usando scripts bash

Consejos para usar tmux

Consejos para usar la pantalla

Shell Scripting para principiantes:cómo escribir Bash Scripts en Linux

¿Usa “${a:-b}” para la asignación de variables en scripts?

Usando el comando Dirname de Linux en Bash Scripts

Uso del comando Bash printf para imprimir salidas formateadas