GNU/Linux >> Tutoriales Linux >  >> Linux

Aprenda el manejo de errores de Bash con el ejemplo

En este artículo, presento algunos trucos para manejar las condiciones de error:algunos estrictamente no entran en la categoría de manejo de errores (una forma reactiva de manejar lo inesperado), pero también algunas técnicas para evitar errores antes de que sucedan.

Estudio de caso:secuencia de comandos simple que descarga un informe de hardware de varios hosts y lo inserta en una base de datos.

Di que tienes un cron trabajo en cada uno de sus sistemas Linux, y tiene un script para recopilar la información de hardware de cada uno:

#!/bin/bash
# Script to collect the status of lshw output from home servers
# Dependencies:
# * LSHW: http://ezix.org/project/wiki/HardwareLiSter
# * JQ: http://stedolan.github.io/jq/
#
# On each machine you can run something like this from cron (Don't know CRON, no worries: https://crontab-generator.org/)
# 0 0 * * * /usr/sbin/lshw -json -quiet > /var/log/lshw-dump.json
# Author: Jose Vicente Nunez
#
declare -a servers=(
dmaf5
)

DATADIR="$HOME/Documents/lshw-dump"

/usr/bin/mkdir -p -v "$DATADIR"
for server in ${servers[*]}; do
    echo "Visiting: $server"
    /usr/bin/scp -o logLevel=Error ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json &
done
wait
for lshw in $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump.json'); do
    /usr/bin/jq '.["product","vendor", "configuration"]' $lshw
done

Si todo va bien, recopila sus archivos en paralelo porque no tiene más de diez sistemas. Puede permitirse enviar ssh a todos ellos al mismo tiempo y luego mostrar los detalles de hardware de cada uno.

Visiting: dmaf5
lshw-dump.json                                                                                         100%   54KB 136.9MB/s   00:00    
"DMAF5 (Default string)"
"BESSTAR TECH LIMITED"
{
  "boot": "normal",
  "chassis": "desktop",
  "family": "Default string",
  "sku": "Default string",
  "uuid": "00020003-0004-0005-0006-000700080009"
}

Aquí hay algunas posibilidades de por qué las cosas salieron mal:

  • Su informe no se ejecutó porque el servidor estaba inactivo
  • No pudo crear el directorio donde se deben guardar los archivos
  • Faltan las herramientas que necesita para ejecutar el script
  • No puede recopilar el informe porque su máquina remota falló
  • Uno o más de los informes están dañados

La versión actual del script tiene un problema:se ejecutará desde el principio hasta el final, con errores o sin ellos:

./collect_data_from_servers.sh 
Visiting: macmini2
Visiting: mac-pro-1-1
Visiting: dmaf5
lshw-dump.json                                                                                         100%   54KB  48.8MB/s   00:00    
scp: /var/log/lshw-dump.json: No such file or directory
scp: /var/log/lshw-dump.json: No such file or directory
parse error: Expected separator between values at line 3, column 9

A continuación, demuestro algunas cosas para hacer que su secuencia de comandos sea más sólida y, en algunos casos, se recupere de fallas.

La opción nuclear:fallar fuerte, fallar rápido

La forma correcta de manejar los errores es verificar si el programa finalizó correctamente o no, utilizando códigos de retorno. Suena obvio, pero los códigos de retorno, un número entero almacenado en bash $? o $! variable, tienen a veces un significado más amplio. La página de manual de bash te dice:

Para los propósitos del shell, un comando que sale con un estado de salida cero
ha tenido éxito. Un estado de salida de cero indica éxito.
Un estado de salida distinto de cero indica falla. Cuando un comando
termina con una señal fatal N, bash usa el valor de 128+N como
el estado de salida.

Como de costumbre, siempre debe leer la página de manual de los scripts que está llamando para ver cuáles son las convenciones para cada uno de ellos. Si ha programado con un lenguaje como Java o Python, lo más probable es que esté familiarizado con sus excepciones, diferentes significados y cómo no todos se manejan de la misma manera.

Si agrega set -o errexit a su secuencia de comandos, a partir de ese momento abortará la ejecución si existe algún comando con un código != 0 . Pero errexit no se usa cuando se ejecutan funciones dentro de un if condición, así que en lugar de recordar esa excepción, prefiero hacer un manejo explícito de errores.

Eche un vistazo a la versión dos del script. Es un poco mejor:

1 #!/bin/bash
2 # Script to collect the status of lshw output from home servers
3 # Dependencies:
4 # * LSHW: http://ezix.org/project/wiki/HardwareLiSter
5 # * JQ: http://stedolan.github.io/jq/
6 #
7 # On each machine you can run something like this from cron (Don't know CRON, no worries: https://crontab-generator.org/        ) 
8 # 0 0 * * * /usr/sbin/lshw -json -quiet > /var/log/lshw-dump.json
9   Author: Jose Vicente Nunez
10 #
11 set -o errtrace # Enable the err trap, code will get called when an error is detected
12 trap "echo ERROR: There was an error in ${FUNCNAME-main context}, details to follow" ERR
13 declare -a servers=(
14 macmini2
15 mac-pro-1-1
16 dmaf5
17 )
18  
19 DATADIR="$HOME/Documents/lshw-dump"
20 if [ ! -d "$DATADIR" ]; then 
21    /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL: Failed to create $DATADIR" && exit 100
22 fi 
23 declare -A server_pid
24 for server in ${servers[*]}; do
25    echo "Visiting: $server"
26    /usr/bin/scp -o logLevel=Error ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json &
27   server_pid[$server]=$! # Save the PID of the scp  of a given server for later
28 done
29 # Iterate through all the servers and:
30 # Wait for the return code of each
31 # Check the exit code from each scp
32 for server in ${!server_pid[*]}; do
33    wait ${server_pid[$server]}
34    test $? -ne 0 && echo "ERROR: Copy from $server had problems, will not continue" && exit 100
35 done
36 for lshw in $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump.json'); do
37    /usr/bin/jq '.["product","vendor", "configuration"]' $lshw
38 done

Esto es lo que cambió:

  • Líneas 11 y 12, habilito el seguimiento de errores y agregué una "trampa" para decirle al usuario que hubo un error y que hay turbulencias por delante. Es posible que desee eliminar su secuencia de comandos aquí en su lugar, le mostraré por qué eso puede no ser lo mejor.
  • Línea 20, si el directorio no existe, intente crearlo en la línea 21. Si falla la creación del directorio, salga con un error.
  • En la línea 27, después de ejecutar cada trabajo en segundo plano, capturo el PID y lo asocio con la máquina (relación 1:1).
  • En las líneas 33-35, espero el scp tarea para finalizar, obtener el código de retorno y, si es un error, cancelar.
  • En la línea 37, compruebo que el archivo se pudo analizar; de lo contrario, salgo con un error.

Entonces, ¿cómo se ve ahora el manejo de errores?

Visiting: macmini2
Visiting: mac-pro-1-1
Visiting: dmaf5
lshw-dump.json                                                                                         100%   54KB 146.1MB/s   00:00    
scp: /var/log/lshw-dump.json: No such file or directory
ERROR: There was an error in main context, details to follow
ERROR: Copy from mac-pro-1-1 had problems, will not continue
scp: /var/log/lshw-dump.json: No such file or directory

Como puede ver, esta versión es mejor para detectar errores, pero es muy implacable. Además, no detecta todos los errores, ¿verdad?

Cuando te quedas atascado y desearías tener una alarma

El código se ve mejor, excepto que a veces el scp podría atascarse en un servidor (al intentar copiar un archivo) porque el servidor está demasiado ocupado para responder o simplemente en mal estado.

Otro ejemplo es intentar acceder a un directorio a través de NFS donde $HOME se monta desde un servidor NFS:

/usr/bin/find $HOME -type f -name '*.csv' -print -fprint /tmp/report.txt

Y horas más tarde descubre que el punto de montaje NFS está obsoleto y su secuencia de comandos está atascada.

Un tiempo de espera es la solución. Y, el tiempo de espera de GNU viene al rescate:

/usr/bin/timeout --kill-after 20.0s 10.0s /usr/bin/find $HOME -type f -name '*.csv' -print -fprint /tmp/report.txt

Aquí intenta matar regularmente (señal TERM) el proceso muy bien después de 10.0 segundos después de que haya comenzado. Si todavía se está ejecutando después de 20,0 segundos, envíe una señal KILL (kill -9 ). En caso de duda, compruebe qué señales son compatibles con su sistema (kill -l , por ejemplo).

Si esto no está claro en mi cuadro de diálogo, mire el guión para obtener más claridad.

/usr/bin/time /usr/bin/timeout --kill-after=10.0s 20.0s /usr/bin/sleep 60s
real    0m20.003s
user    0m0.000s
sys     0m0.003s

Vuelva a la secuencia de comandos original para agregar algunas opciones más y tendrá la versión tres:

 1 #!/bin/bash
  2 # Script to collect the status of lshw output from home servers
  3 # Dependencies:
  4 # * Open SSH: http://www.openssh.com/portable.html
  5 # * LSHW: http://ezix.org/project/wiki/HardwareLiSter
  6 # * JQ: http://stedolan.github.io/jq/
  7 # * timeout: https://www.gnu.org/software/coreutils/
  8 #
  9 # On each machine you can run something like this from cron (Don't know CRON, no worries: https://crontab-generator.org/)
 10 # 0 0 * * * /usr/sbin/lshw -json -quiet > /var/log/lshw-dump.json
 11 # Author: Jose Vicente Nunez
 12 #
 13 set -o errtrace # Enable the err trap, code will get called when an error is detected
 14 trap "echo ERROR: There was an error in ${FUNCNAME-main context}, details to follow" ERR
 15 
 16 declare -a dependencies=(/usr/bin/timeout /usr/bin/ssh /usr/bin/jq)
 17 for dependency in ${dependencies[@]}; do
 18     if [ ! -x $dependency ]; then
 19         echo "ERROR: Missing $dependency"
 20         exit 100
 21     fi
 22 done
 23 
 24 declare -a servers=(
 25 macmini2
 26 mac-pro-1-1
 27 dmaf5
 28 )
 29 
 30 function remote_copy {
 31     local server=$1
 32     echo "Visiting: $server"
 33     /usr/bin/timeout --kill-after 25.0s 20.0s \
 34         /usr/bin/scp \
 35             -o BatchMode=yes \
 36             -o logLevel=Error \
 37             -o ConnectTimeout=5 \
 38             -o ConnectionAttempts=3 \
 39             ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json
 40     return $?
 41 }
 42 
 43 DATADIR="$HOME/Documents/lshw-dump"
 44 if [ ! -d "$DATADIR" ]; then
 45     /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL: Failed to create $DATADIR" && exit 100
 46 fi
 47 declare -A server_pid
 48 for server in ${servers[*]}; do
 49     remote_copy $server &
 50     server_pid[$server]=$! # Save the PID of the scp  of a given server for later
 51 done
 52 # Iterate through all the servers and:
 53 # Wait for the return code of each
 54 # Check the exit code from each scp
 55 for server in ${!server_pid[*]}; do
 56     wait ${server_pid[$server]}
 57     test $? -ne 0 && echo "ERROR: Copy from $server had problems, will not continue" && exit 100
 58 done
 59 for lshw in $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump.json'); do
 60     /usr/bin/jq '.["product","vendor", "configuration"]' $lshw
 61 done

¿Cuáles son los cambios?:

  • Entre las líneas 16 a 22, verifique si todas las herramientas de dependencia requeridas están presentes. Si no se puede ejecutar, entonces "Houston, tenemos un problema".
  • Creó una remote_copy función, que utiliza un tiempo de espera para asegurarse de que scp termina no más tarde de 45.0s—línea 33.
  • Se agregó un tiempo de espera de conexión de 5 segundos en lugar del valor predeterminado de TCP:línea 37.
  • Se agregó un reintento a scp en la línea 38:3 intentos que esperan 1 segundo entre cada uno.

Hay otras formas de volver a intentarlo cuando hay un error.

Esperando el fin del mundo:cómo y cuándo volver a intentarlo

Notaste que hay un reintento agregado al scp dominio. Pero eso reintenta solo para conexiones fallidas, ¿qué pasa si el comando falla durante la mitad de la copia?

A veces, simplemente desea fallar porque hay muy pocas posibilidades de recuperarse de un problema. Un sistema que requiere reparaciones de hardware, por ejemplo, o simplemente puede volver a un modo degradado, lo que significa que puede continuar con el funcionamiento de su sistema sin los datos actualizados. En esos casos, no tiene sentido esperar para siempre sino solo por un período de tiempo específico.

Estos son los cambios en remote_copy , para ser breve (versión cuatro):

#!/bin/bash
# Omitted code for clarity...
declare REMOTE_FILE="/var/log/lshw-dump.json"
declare MAX_RETRIES=3

# Blah blah blah...

function remote_copy {
    local server=$1
    local retries=$2
    local now=1
    status=0
    while [ $now -le $retries ]; do
        echo "INFO: Trying to copy file from: $server, attempt=$now"
        /usr/bin/timeout --kill-after 25.0s 20.0s \
            /usr/bin/scp \
                -o BatchMode=yes \
                -o logLevel=Error \
                -o ConnectTimeout=5 \
                -o ConnectionAttempts=3 \
                ${server}:$REMOTE_FILE ${DATADIR}/lshw-$server-dump.json
        status=$?
        if [ $status -ne 0 ]; then
            sleep_time=$(((RANDOM % 60)+ 1))
            echo "WARNING: Copy failed for $server:$REMOTE_FILE. Waiting '${sleep_time} seconds' before re-trying..."
            /usr/bin/sleep ${sleep_time}s
        else
            break # All good, no point on waiting...
        fi
        ((now=now+1))
    done
    return $status
}

DATADIR="$HOME/Documents/lshw-dump"
if [ ! -d "$DATADIR" ]; then
    /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL: Failed to create $DATADIR" && exit 100
fi
declare -A server_pid
for server in ${servers[*]}; do
    remote_copy $server $MAX_RETRIES &
    server_pid[$server]=$! # Save the PID of the scp  of a given server for later
done

# Iterate through all the servers and:
# Wait for the return code of each
# Check the exit code from each scp
for server in ${!server_pid[*]}; do
    wait ${server_pid[$server]}
    test $? -ne 0 && echo "ERROR: Copy from $server had problems, will not continue" && exit 100
done

# Blah blah blah, process the files you just copied...

¿Cómo se ve ahora? En esta ejecución, tengo un sistema inactivo (mac-pro-1-1) y un sistema sin el archivo (macmini2). Puede ver que la copia del servidor dmaf5 funciona de inmediato, pero para los otros dos, hay un reintento aleatorio entre 1 y 60 segundos antes de salir:

INFO: Trying to copy file from: macmini2, attempt=1
INFO: Trying to copy file from: mac-pro-1-1, attempt=1
INFO: Trying to copy file from: dmaf5, attempt=1
scp: /var/log/lshw-dump.json: No such file or directory
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for macmini2:/var/log/lshw-dump.json. Waiting '60 seconds' before re-trying...
ssh: connect to host mac-pro-1-1 port 22: No route to host
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for mac-pro-1-1:/var/log/lshw-dump.json. Waiting '32 seconds' before re-trying...
INFO: Trying to copy file from: mac-pro-1-1, attempt=2
ssh: connect to host mac-pro-1-1 port 22: No route to host
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for mac-pro-1-1:/var/log/lshw-dump.json. Waiting '18 seconds' before re-trying...
INFO: Trying to copy file from: macmini2, attempt=2
scp: /var/log/lshw-dump.json: No such file or directory
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for macmini2:/var/log/lshw-dump.json. Waiting '3 seconds' before re-trying...
INFO: Trying to copy file from: macmini2, attempt=3
scp: /var/log/lshw-dump.json: No such file or directory
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for macmini2:/var/log/lshw-dump.json. Waiting '6 seconds' before re-trying...
INFO: Trying to copy file from: mac-pro-1-1, attempt=3
ssh: connect to host mac-pro-1-1 port 22: No route to host
ERROR: There was an error in main context, details to follow
WARNING: Copy failed for mac-pro-1-1:/var/log/lshw-dump.json. Waiting '47 seconds' before re-trying...
ERROR: There was an error in main context, details to follow
ERROR: Copy from mac-pro-1-1 had problems, will not continue

Si fallo, ¿tengo que hacer todo esto de nuevo? Usando un punto de control

Suponga que la copia remota es la operación más costosa de todo este script y que está dispuesto o puede volver a ejecutar este script, tal vez usando cron o hacerlo a mano dos veces durante el día para asegurarse de recoger los archivos si uno o más sistemas no funcionan.

Podría, para el día, crear un pequeño "caché de estado", donde registre solo las operaciones de procesamiento exitosas por máquina. Si hay un sistema allí, no se moleste en volver a comprobar ese día.

Algunos programas, como Ansible, hacen algo similar y le permiten volver a intentar un libro de jugadas en un número limitado de máquinas después de una falla (--limit @/home/user/site.retry ).

Una nueva versión (versión cinco) del script tiene código para registrar el estado de la copia (líneas 15-33):

15 declare SCRIPT_NAME=$(/usr/bin/basename $BASH_SOURCE)|| exit 100
16 declare YYYYMMDD=$(/usr/bin/date +%Y%m%d)|| exit 100
17 declare CACHE_DIR="/tmp/$SCRIPT_NAME/$YYYYMMDD"
18 # Logic to clean up the cache dir on daily basis is not shown here
19 if [ ! -d "$CACHE_DIR" ]; then
20   /usr/bin/mkdir -p -v "$CACHE_DIR"|| exit 100
21 fi
22 trap "/bin/rm -rf $CACHE_DIR" INT KILL
23
24 function check_previous_run {
25  local machine=$1
26  test -f $CACHE_DIR/$machine && return 0|| return 1
27 }
28
29 function mark_previous_run {
30    machine=$1
31    /usr/bin/touch $CACHE_DIR/$machine
32    return $?
33 }

¿Te diste cuenta de la trampa en la línea 22? Si la secuencia de comandos se interrumpe (mata), quiero asegurarme de que se invalide todo el caché.

Y luego, agregue esta nueva lógica auxiliar en remote_copy función (líneas 52-81):

52 function remote_copy {
53    local server=$1
54    check_previous_run $server
55    test $? -eq 0 && echo "INFO: $1 ran successfully before. Not doing again" && return 0
56    local retries=$2
57    local now=1
58    status=0
59    while [ $now -le $retries ]; do
60        echo "INFO: Trying to copy file from: $server, attempt=$now"
61        /usr/bin/timeout --kill-after 25.0s 20.0s \
62            /usr/bin/scp \
63                -o BatchMode=yes \
64                -o logLevel=Error \
65                -o ConnectTimeout=5 \
66               -o ConnectionAttempts=3 \
67                ${server}:$REMOTE_FILE ${DATADIR}/lshw-$server-dump.json
68        status=$?
69        if [ $status -ne 0 ]; then
70            sleep_time=$(((RANDOM % 60)+ 1))
71            echo "WARNING: Copy failed for $server:$REMOTE_FILE. Waiting '${sleep_time} seconds' before re-trying..."
72            /usr/bin/sleep ${sleep_time}s
73        else
74            break # All good, no point on waiting...
75        fi
76        ((now=now+1))
77    done
78    test $status -eq 0 && mark_previous_run $server
79    test $? -ne 0 && status=1
80    return $status
81 }

La primera vez que se ejecuta, se imprime un nuevo mensaje nuevo para el directorio de caché:

./collect_data_from_servers.v5.sh
/usr/bin/mkdir: created directory '/tmp/collect_data_from_servers.v5.sh'
/usr/bin/mkdir: created directory '/tmp/collect_data_from_servers.v5.sh/20210612'
ERROR: There was an error in main context, details to follow
INFO: Trying to copy file from: macmini2, attempt=1
ERROR: There was an error in main context, details to follow

Si lo vuelve a ejecutar, la secuencia de comandos sabrá que dma5f está listo, no es necesario volver a intentar la copia:

./collect_data_from_servers.v5.sh
INFO: dmaf5 ran successfully before. Not doing again
ERROR: There was an error in main context, details to follow
INFO: Trying to copy file from: macmini2, attempt=1
ERROR: There was an error in main context, details to follow
INFO: Trying to copy file from: mac-pro-1-1, attempt=1

Imagínese cómo se acelera esto cuando tiene más máquinas que no deben revisarse.

Dejando migas atrás:qué registrar, cómo registrar y resultados detallados

Si eres como yo, me gusta un poco de contexto para correlacionar cuando algo sale mal. El echo Las declaraciones en el script son buenas, pero ¿qué pasaría si pudieras agregarles una marca de tiempo?

Si usa logger , puede guardar el resultado en journalctl para una revisión posterior (incluso la agregación con otras herramientas disponibles). La mejor parte es que muestras el poder de journalctl enseguida.

Entonces, en lugar de simplemente hacer echo , también puede agregar una llamada a logger así usando una nueva función bash llamada 'message ':

SCRIPT_NAME=$(/usr/bin/basename $BASH_SOURCE)|| exit 100
FULL_PATH=$(/usr/bin/realpath ${BASH_SOURCE[0]})|| exit 100
set -o errtrace # Enable the err trap, code will get called when an error is detected
trap "echo ERROR: There was an error in ${FUNCNAME[0]-main context}, details to follow" ERR
declare CACHE_DIR="/tmp/$SCRIPT_NAME/$YYYYMMDD"

function message {
    message="$1"
    func_name="${2-unknown}"
    priority=6
    if [ -z "$2" ]; then
        echo "INFO:" $message
    else
        echo "ERROR:" $message
        priority=0
    fi
    /usr/bin/logger --journald<<EOF
MESSAGE_ID=$SCRIPT_NAME
MESSAGE=$message
PRIORITY=$priority
CODE_FILE=$FULL_PATH
CODE_FUNC=$func_name
EOF
}

Puede ver que puede almacenar campos separados como parte del mensaje, como la prioridad, la secuencia de comandos que produjo el mensaje, etc.

Entonces, ¿cómo es esto útil? Bueno, podrías get los mensajes entre las 13:26 y las 13:27, solo errores (priority=0 ) y solo para nuestro script (collect_data_from_servers.v6.sh ) así, salida en formato JSON:

journalctl --since 13:26 --until 13:27 --output json-pretty PRIORITY=0 MESSAGE_ID=collect_data_from_servers.v6.sh
{
        "_BOOT_ID" : "dfcda9a1a1cd406ebd88a339bec96fb6",
        "_AUDIT_LOGINUID" : "1000",
        "SYSLOG_IDENTIFIER" : "logger",
        "PRIORITY" : "0",
        "_TRANSPORT" : "journal",
        "_SELINUX_CONTEXT" : "unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023",
        "__REALTIME_TIMESTAMP" : "1623518797641880",
        "_AUDIT_SESSION" : "3",
        "_GID" : "1000",
        "MESSAGE_ID" : "collect_data_from_servers.v6.sh",
        "MESSAGE" : "Copy failed for macmini2:/var/log/lshw-dump.json. Waiting '45 seconds' before re-trying...",
        "_CAP_EFFECTIVE" : "0",
        "CODE_FUNC" : "remote_copy",
        "_MACHINE_ID" : "60d7a3f69b674aaebb600c0e82e01d05",
        "_COMM" : "logger",
        "CODE_FILE" : "/home/josevnz/BashError/collect_data_from_servers.v6.sh",
        "_PID" : "41832",
        "__MONOTONIC_TIMESTAMP" : "25928272252",
        "_HOSTNAME" : "dmaf5",
        "_SOURCE_REALTIME_TIMESTAMP" : "1623518797641843",
        "__CURSOR" : "s=97bb6295795a4560ad6fdedd8143df97;i=1f826;b=dfcda9a1a1cd406ebd88a339bec96fb6;m=60972097c;t=5c494ed383898;x=921c71966b8943e3",
        "_UID" : "1000"
}

Debido a que se trata de datos estructurados, otros recopiladores de registros pueden revisar todas sus máquinas, agregar sus registros de secuencias de comandos y luego no solo tiene datos sino también la información.

Puede echar un vistazo a la versión seis completa del script.

No esté tan ansioso por reemplazar sus datos hasta que los haya verificado.


Si se dio cuenta desde el principio, he estado copiando un archivo JSON dañado una y otra vez:

Parse error: Expected separator between values at line 4, column 11
ERROR parsing '/home/josevnz/Documents/lshw-dump/lshw-dmaf5-dump.json'

Eso es fácil de prevenir. Copie el archivo en una ubicación temporal y, si el archivo está dañado, no intente reemplazar la versión anterior (y deje la mala para que la inspeccionen. Líneas 99 a 107 de la versión siete del script):

function remote_copy {
    local server=$1
    check_previous_run $server
    test $? -eq 0 && message "$1 ran successfully before. Not doing again" && return 0
    local retries=$2
    local now=1
    status=0
    while [ $now -le $retries ]; do
        message "Trying to copy file from: $server, attempt=$now"
        /usr/bin/timeout --kill-after 25.0s 20.0s \
            /usr/bin/scp \
                -o BatchMode=yes \
                -o logLevel=Error \
                -o ConnectTimeout=5 \
                -o ConnectionAttempts=3 \
                ${server}:$REMOTE_FILE ${DATADIR}/lshw-$server-dump.json.$$
        status=$?
        if [ $status -ne 0 ]; then
            sleep_time=$(((RANDOM % 60)+ 1))
            message "Copy failed for $server:$REMOTE_FILE. Waiting '${sleep_time} seconds' before re-trying..." ${FUNCNAME[0]}
            /usr/bin/sleep ${sleep_time}s
        else
            break # All good, no point on waiting...
        fi
        ((now=now+1))
    done
    if [ $status -eq 0 ]; then
        /usr/bin/jq '.' ${DATADIR}/lshw-$server-dump.json.$$ > /dev/null 2>&1
        status=$?
        if [ $status -eq 0 ]; then
            /usr/bin/mv -v -f ${DATADIR}/lshw-$server-dump.json.$$ ${DATADIR}/lshw-$server-dump.json && mark_previous_run $server
            test $? -ne 0 && status=1
        else
            message "${DATADIR}/lshw-$server-dump.json.$$ Is corrupted. Leaving for inspection..." ${FUNCNAME[0]}
        fi
    fi
    return $status
}

Elige las herramientas adecuadas para la tarea y prepara tu código desde la primera línea

Un aspecto muy importante del manejo de errores es la codificación adecuada. Si tiene una mala lógica en su código, ninguna cantidad de manejo de errores lo mejorará. Para mantener esto breve y relacionado con bash, a continuación le daré algunos consejos.

SIEMPRE debe verificar la sintaxis de errores antes de ejecutar su secuencia de comandos:

bash -n $my_bash_script.sh

En serio. Debería ser tan automático como realizar cualquier otra prueba.

Lea la página de manual de bash y familiarícese con las opciones imprescindibles, como:

set -xv
my_complicated_instruction1
my_complicated_instruction2
my_complicated_instruction3
set +xv

Use ShellCheck para verificar sus scripts bash

Es muy fácil pasar por alto problemas simples cuando sus scripts comienzan a crecer. ShellCheck es una de esas herramientas que le evita cometer errores.

shellcheck collect_data_from_servers.v7.sh

In collect_data_from_servers.v7.sh line 15:
for dependency in ${dependencies[@]}; do
                  ^----------------^ SC2068: Double quote array expansions to avoid re-splitting elements.


In collect_data_from_servers.v7.sh line 16:
    if [ ! -x $dependency ]; then
              ^---------^ SC2086: Double quote to prevent globbing and word splitting.

Did you mean: 
    if [ ! -x "$dependency" ]; then
...

Si se lo pregunta, la versión final del script, después de pasar ShellCheck, está aquí. Impecablemente limpio.

Notaste algo con los procesos de scp en segundo plano

Probablemente haya notado que si mata el script, deja atrás algunos procesos bifurcados. Eso no es bueno y esta es una de las razones por las que prefiero usar herramientas como Ansible o Parallel para manejar este tipo de tareas en múltiples hosts, dejando que los marcos hagan la limpieza adecuada por mí. Por supuesto, puede agregar más código para manejar esta situación.

Este script bash podría potencialmente crear una bomba de bifurcación. No tiene control sobre cuántos procesos generar al mismo tiempo, lo cual es un gran problema en un entorno de producción real. Además, hay un límite en la cantidad de sesiones ssh simultáneas que puede tener (y mucho menos consumir ancho de banda). Nuevamente, escribí este ejemplo ficticio en bash para mostrarle cómo siempre puede mejorar un programa para manejar mejor los errores.

Recapitulemos

[ Descargar ahora:una guía para administradores de sistemas sobre secuencias de comandos Bash. ]

1. Debes comprobar el código de retorno de tus comandos. Eso podría significar decidir volver a intentarlo hasta que mejore una condición transitoria o interrumpir todo el script.
2. Hablando de condiciones transitorias, no es necesario empezar de cero. Puede guardar el estado de las tareas exitosas y luego volver a intentarlo a partir de ese momento.
3. Bash 'trap' es tu amigo. Úselo para limpieza y manejo de errores.
4. Al descargar datos de cualquier fuente, asuma que están dañados. Nunca sobrescriba su buen conjunto de datos con datos nuevos hasta que haya realizado algunas comprobaciones de integridad.
5. Aproveche journalctl y campos personalizados. Puede realizar búsquedas sofisticadas en busca de problemas e incluso enviar esos datos a los agregadores de registros.
6. Puede comprobar el estado de las tareas en segundo plano (incluidas las subcapas). Solo recuerde guardar el PID y esperarlo.
7. Y finalmente:use un asistente de pelusa Bash como ShellCheck. Puede instalarlo en su editor favorito (como VIM o PyCharm). Se sorprenderá de cuántos errores no se detectan en los scripts de Bash...

Si disfrutó de este contenido o desea ampliarlo, comuníquese con el equipo en [email protected].


Linux
  1. Typeset -a ¿Está dando un error en el script?

  2. ¿Los mejores recursos para aprender Bash Scripting?

  3. Personalización de Bash Shell:Negrita/color ¿El comando?

  4. Error de secuencia de comandos Bash:¿Se esperaba una expresión entera?

  5. Bash:¿Error de sintaxis cerca del token inesperado '}'?

Bash Shebang

Manejo de errores en scripts Bash

Solución de problemas del error "Bash:Comando no encontrado" en Linux

¿La variable Curl Outfile no funciona en Bash Script?

Aprenda secuencias de comandos Bash multihilo con GNU Parallel

Parámetros especiales de Bash explicados con 4 scripts de Shell de ejemplo