
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>&1
se 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>&1
el script y la copia continuó como se esperaba.
Pregunta: ¿Mi log
funció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 log
funció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 cp
emite un primer mensaje de error, la log
función lo lee y lo procesa. Dado que la log
funció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 cp
emite 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 cp
no intenta cambiar eso), por lo que el almacenamiento en búfer no entra en juego.
Para procesar todas las líneas de entrada, necesita read
un bucle.
log () {
while IFS= read -r IN; do
echo "$datetime"$'\t'"$IN"
done | tee -a logfile >&2
}
También arreglé la read
llamada aIFS= read -r
para 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 read
comando. También arreglé log
la 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 cp
falla, 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 elpipefail
opciónademás de set -e
para 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 log
se ignoran efectivamente y el comando puede regresar antes de que log
se haya completado (en bash, log
se ejecuta en segundo plano).
set -e
copy … 2> >(log)
¹ Casi, nunca necesitarías IFS= read -r IN
leer más y posiblemente no estropear la línea.