2010-12-20 11 views
18

(posiblemente relacionado con Do some programs not accept process substitution for input files?)Bash sustitución de procesos y sincronización

En algunos scripts de prueba de unidad Bash estoy usando el siguiente truco para conectarse y stdout y stderr pantalla de un comando:

command > >(tee "${stdoutF}") 2> >(tee "${stderrF}" >&2) 

Este proceso produce algunos resultados en stdout, por lo que el archivo $stdoutF obtiene algunos datos. Luego ejecutar otro comando que no hace ninguna salida de datos:

diff -r "$source" "$target" > >(tee "${stdoutF}") 2> >(tee "${stderrF}" >&2) 

Sin embargo, no parece que este proceso siempre se realiza correctamente antes de la prueba de vacío se ejecuta (usando shunit-ng):

assertNull 'Unexpected output to stdout' "$(<"$stdoutF")" 

En una prueba de 100 intentos, esto falló 25 veces.

¿Debería ser suficiente para llamar sync antes de probar el archivo para el vacío:

sync 
assertNull 'Unexpected output to stdout' "$(<"$stdoutF")" 

... y/o debe trabajar al obligar a la secuencia de los comandos:

diff -r "$source" "$target" \ 
> >(tee "${stdoutF}"; assertNull 'Unexpected output to stdout' "$(<"$stdoutF")") 
2> >(tee "${stderrF}" >&2) 

. ¿y/o es posible tee de alguna manera al assertNull directamente en lugar de un archivo?

Actualización: sync no es la respuesta - Consulte la respuesta de Gilles a continuación.

Actualización 2: Discusión realizada más allá de Save stdout, stderr and stdout+stderr synchronously. Gracias por las respuestas!

Respuesta

27

En bash, un comando Cambio proceso foo > >(bar) acabados tan pronto como foo acabados. (Esto no se discute en la documentación.) Puede comprobar esto con

: > >(sleep 1; echo a) 

Este comando vuelve inmediatamente, luego imprime de forma asíncrona a un segundo más tarde.

En su caso, el comando tee toma solo un poco de tiempo para terminar después de completar command. Agregar sync dio tee tiempo suficiente para completar, pero esto no elimina la condición de carrera, más que agregar sleep haría, simplemente hace que la raza sea más improbable que se manifieste.

De manera más general, sync no tiene ningún efecto observable internamente: solo hace la diferencia si quiere acceder al dispositivo donde sus sistemas de archivos están almacenados en una instancia diferente del sistema operativo. En términos más claros, si su sistema pierde potencia, solo se garantiza que estarán disponibles los datos escritos antes de la última sync después de reiniciar.

En cuanto a la eliminación de la condición de carrera, aquí están algunos de los posibles enfoques:

  • sincronizar de forma explícita todos los procesos sustituidos.

    mkfifo sync.pipe 
    command > >(tee -- "$stdoutF"; echo >sync.pipe) 
         2> >(tee -- "$stderrF"; echo >sync.pipe) 
    read line < sync.pipe; read line < sync.pipe 
    
  • utilizar un nombre de archivo temporal diferente para cada comando en lugar de reutilizar $stdoutF y $stderrF, y hacer cumplir que el archivo temporal siempre se acaba de crear.

  • Olvídate de la sustitución de procesos y utiliza tuberías en su lugar.

    { { command | tee -- "$stdoutF" 1>&3; } 2>&1 \ 
          | tee -- "$stderrF" 1>&2; } 3>&1 
    

    Si necesitas estado de retorno del comando, fiesta pone en ${PIPESTATUS[0]}.

    { { command | tee -- "$stdoutF" 1>&3; exit ${PIPESTATUS[0]}; } 2>&1 \ 
          | tee -- "$stderrF" 1>&2; } 3>&1 
    if [ ${PIPESTATUS[0]} -ne 0 ]; then echo command failed; fi 
    
+0

Gracias por esta respuesta impresionante, esto sólo me ha hecho una mejor administración! –

-1

Inserte una sleep 5 o lo que sea en su lugar de sync para responder a su última pregunta

1

veces pongo un guardia:

: > >(sleep 1; echo a; touch guard) \ 
    && while true; do 
    [ -f "guard" ] && { rm guard; break; } 
    sleep 0.2 
    done  
Cuestiones relacionadas