Los errores de tubería a mi función hacen que el comando se ignore después de 1 falla

Los errores de tubería a mi función hacen que el comando se ignore después de 1 falla

Contexto: tengo un script Bash que copia archivos

function log () {
    read IN
    if [ "$IN" == "" ]; then
        :
    else

        echo "$datetime"$'\t'"$IN" | tee -a logfile
    fi
}

function copy () {
    command cp -L --parents $@
}

...
copy -R /etc . 2>&1 | log
...

Problema: cuando cp -L --parents -R /etc 2>&1se ejecuta manualmente, obtengo alrededor de 10 fallas (enlaces simbólicos rotos, se espera) y se ha copiado todo el /etc.

Pero cuando se ejecuta el script, solo se informa 1 error y /etc solo se copia hasta el punto donde ocurrió el 1 error.

En el intento de solucionar el problema, todo lo que hice fue eliminar 2>&1el script y la copia continuó como se esperaba.

Pregunta: ¿Mi logfunción está causando el problema o se trata de algún problema sintáctico (aunque no rompe el guión) en la forma en que está escrito el guión?

Respuesta1

Tu logfunción es la culpable. Lo que hace es: leer una línea¹, y si la línea no está vacía, imprime una marca de tiempo y luego el contenido de la línea. Eso es todo lo que hace: una vez procesada una línea, regresa.

Cuando cpemite un primer mensaje de error, la logfunción lo lee y lo procesa. Dado que la logfunción regresa, el proceso en el lado derecho de la tubería sale, lo que hace que el extremo de lectura de la tubería se cierre. Cuando cpemite un segundo mensaje de error, intenta escribir en una tubería cerrada, lo que provoca que muera por unseñal SIGPIPE. El error estándar tiene un búfer de línea (de forma predeterminada y cpno intenta cambiar eso), por lo que el almacenamiento en búfer no entra en juego.

Para procesar todas las líneas de entrada, necesita readun bucle.

log () {
    while IFS= read -r IN; do
        echo "$datetime"$'\t'"$IN"
    done | tee -a logfile >&2
}

También arreglé la readllamada aIFS= read -rpara leer realmente una línea. Eliminé el manejo especial para líneas vacías, lo cual no tiene sentido (no habría líneas vacías en la entrada). Supongo que lo puso para manejar el caso cuando la entrada está vacía (cero líneas de entrada), pero la forma correcta de manejar eso es verificar el estado de retorno del readcomando. También arreglé logla impresión con su error estándar, ya que se usa para procesar mensajes de error.

VerAnteponer una marca de tiempo a cada línea de salida de un comandopara otras formas de hacer esto.

Tenga en cuenta que poner el comando en el lado izquierdo de una tubería tiene una desventaja importante:hace que se ignore el estado de salida. Entonces, si cpfalla, su guión continuará felizmente. Los errores se registrarán en algún lugar, pero los comandos posteriores se ejecutarán normalmente y nada le alertará sobre el hecho de que debe leer los registros. En bash, ksh o zsh, puedes configurar elpipefailopciónademás de set -epara que su script salga con un estado de error tan pronto como falle cualquier comando, incluso en el lado izquierdo de una canalización.

set -o errexit -o pipefail
copy … |& log

Alternativamente, usesustitución de procesosen lugar de una tubería para canalizar la salida del error a través de otro proceso. La sustitución de procesos tiene advertencias ligeramente diferentes a las de las tuberías; Los errores logse ignoran efectivamente y el comando puede regresar antes de que logse haya completado (en bash, logse ejecuta en segundo plano).

set -e
copy … 2> >(log)

¹ Casi, nunca necesitarías IFS= read -r INleer más y posiblemente no estropear la línea.

información relacionada