¿Cómo capturo el código de salida/manejo los errores correctamente cuando utilizo la sustitución de procesos?

¿Cómo capturo el código de salida/manejo los errores correctamente cuando utilizo la sustitución de procesos?

Tengo un script que analiza los nombres de los archivos en una matriz usando el siguiente método tomado deuna sesión de preguntas y respuestas sobre SO:

unset ARGS
ARGID="1"
while IFS= read -r -d $'\0' FILE; do
    ARGS[ARGID++]="$FILE"
done < <(find "$@" -type f -name '*.txt' -print0)

Esto funciona muy bien y maneja perfectamente todo tipo de variaciones de nombres de archivos. A veces, sin embargo, paso un archivo inexistente al script, por ejemplo:

$ findscript.sh existingfolder nonexistingfolder
find: `nonexistingfile': No such file or directory
...

En circunstancias normales, haría que el script capturara el código de salida con algo así RET=$?y lo usara para decidir cómo proceder. Esto no parece funcionar con el proceso de sustitución anterior.

¿Cuál es el procedimiento correcto en casos como este? ¿Cómo puedo capturar el código de retorno? ¿Existen otras formas más adecuadas de determinar si algo salió mal en el proceso de sustitución?

Respuesta1

Puede obtener con bastante facilidad el retorno de cualquier proceso subcapa haciendo eco de su retorno a través de su salida estándar. Lo mismo ocurre con la sustitución de procesos:

while IFS= read -r -d $'\0' FILE || 
    ! return=$FILE
do    ARGS[ARGID++]="$FILE"
done < <(find . -type f -print0; printf "$?")

Si ejecuto eso, entonces la última línea...(o \0sección delimitada según sea el caso)va a ser findel estado de devolución. readdevolverá 1 cuando obtenga un EOF, por lo que el único momento $returnestablecido $FILEes para la última información leída.

Solía printf​​​​evitar agregar una \nlínea ew adicional; esto es importante porque incluso una readrealizada regularmente, una en la que no se delimitan los \0NUL, devolverá un valor distinto de 0 en los casos en que los datos que acaba de leer no terminen en una \nlínea de ew. Entonces, si su última línea no termina con una \nlínea ew, el último valor en su variable leída será su retorno.

Ejecutando el comando anterior y luego:

echo "$return"

PRODUCCIÓN

0

Y si altero la parte de sustitución del proceso...

...
done < <(! find . -type f -print0; printf "$?")
echo "$return"

PRODUCCIÓN

1

Una demostración más sencilla:

printf \\n%s list of lines printed to pipe |
while read v || ! echo "$v"
do :; done

PRODUCCIÓN

pipe

Y de hecho, siempre que la devolución que desee sea lo último que escriba en la salida estándar desde la sustitución del proceso (o cualquier proceso subcapa del que lea de esta manera), $FILEsiempre será el estado de devolución que desee cuando es a través. Por lo tanto, la || ! return=...parte no es estrictamente necesaria: se utiliza únicamente para demostrar el concepto.

Respuesta2

Usar unacoproceso. Usando el coprocincorporado puedes iniciar un subproceso, leer su salida y verificar su estado de salida:

coproc LS { ls existingdir; }
LS_PID_=$LS_PID
while IFS= read i; do echo "$i"; done <&"$LS"
wait "$LS_PID_"; echo $?

Si el directorio no existe, waitsaldrá con un código de estado distinto de cero.

Actualmente es necesario copiar el PID a otra variable porque $LS_PIDse desarmará antes de que waitse llame. VerBash desactiva la variable *_PID antes de que pueda esperar en coprocpara detalles.

Respuesta3

Los procesos en sustitución de procesos son asincrónicos: el shell los inicia y luego no ofrece ninguna forma de detectar cuándo mueren. Por lo tanto no podrá obtener el estado de salida.

Puede escribir el estado de salida en un archivo, pero en general esto es complicado porque no puede saber cuándo se escribe el archivo. Aquí, el archivo se escribe poco después del final del ciclo, por lo que es razonable esperarlo.

… < <(find …; echo $? >find.status.tmp; mv find.status.tmp find.status)
while ! [ -e find.status ]; do sleep 1; done
find_status=$(cat find.status; rm find.status)

Otro enfoque es utilizar una canalización con nombre y un proceso en segundo plano (para lo cual puede hacerlo wait).

mkfifo find_pipe
find … >find_pipe &
find_pid=$!
… <find_pipe
wait $find_pid
find_status=$?

Si ninguno de los dos enfoques es adecuado, creo que tendrás que optar por un lenguaje más capaz, como Perl, Python o Ruby.

Respuesta4

Un enfoque es:

status=0
token="WzNZY3CjqF3qkasn"    # some random string
while read line; do
    if [[ "$line" =~ $token:([[:digit:]]+) ]]; then
        status="${BASH_REMATCH[1]}"
    else
        echo "$line"
    fi
done < <(command; echo "$token:$?")
echo "Return code: $status"

La idea es hacer eco del estado de salida junto con el token aleatorio después de que se haya completado el comando y luego usar expresiones regulares de bash para buscar y extraer el estado de salida. El token se utiliza para crear una cadena única que se buscará en la salida.

Probablemente no sea la mejor manera de hacerlo en un sentido general de programación, pero podría ser la forma menos dolorosa de manejarlo en bash.

información relacionada