control de secuencia para errores en tubería sin nombre

control de secuencia para errores en tubería sin nombre
$ awk -v f=<(cmdmayfail) -e 'BEGIN { r = getline < f ; print r }'
-bash: cmdmayfail: command not found
0

En el ejemplo de canalización sin nombre anterior, awkno reconocerá el error de la canalización sin nombre.

$ awk -v f=<(cmdmayfail || echo "control sequence" ) -e 'BEGIN { r = getline < f ; print r }'

Para darme awkcuenta de este error, podría usar el código anterior enviando una secuencia de control y alguna información de error.

Dado que fileya conoce muchos tipos de archivos, ¿existe una secuencia de control razonable que pueda usarse para esta aplicación de modo que awkno maltrate la secuencia de control de errores como si fuera un archivo legítimo? Gracias.

Respuesta1

Si se commandmayfailtrata de un comando estándar de Unix, anteponer la secuencia de control con su propio texto complejo (por ejemplo __ERROR__CMDFAIL__:) debería ser suficiente para awkcomprender la diferencia.

Sin embargo, si también incluye software propio y/o propietario, es difícil darle una cadena general. Es posible (aunque poco probable) que uno de sus comandos de propiedad utilice dicha cadena. Debe observar la configuración general de los mensajes de error y crear una cadena que es poco probable que se utilice.

Si commandmayfailes file, como sugiere la pregunta, puede ser suficiente usar una cadena sin :.

Respuesta2

Si el resultado de cmdmayfailes relativamente pequeño y el comando en sí termina independientemente de otras partes de su código, entonces puede almacenar su resultado en una variable y pasar el estado de salida como la primera línea. El código <()sería como:

out="$(cmdmayfail)"; printf '%s\n' "$?" "$out"

Debería obtener el estado de salida awk. getline<fConsecutive getline<fleerá la salida real de cmdmayfail.

Limitaciones:

  • Las variables en Bash no pueden almacenar caracteres nulos.
  • $()eliminará todos los caracteres de nueva línea finales; luego printfagregará exactamente uno. Un truco engorroso para evitar esto:

    out="$(cmdmayfail; s="$?"; printf X; exit "$s")"; printf '%s\n%s' "$?" "${out%X}"
    

El hecho de que maneje la salida cmdmayfaildel BEGINbloque puede indicar que espera cmdmayfailterminar antes de tiempo. Quizás esta solución sea suficiente.


En general, cmdmayfailpuede incluso ejecutarse "sin fin" (es decir, hasta que lo finalice) y es posible que desee leer su salida mientras procesa la entrada estándar ("interminable") de awk. En tal caso, la solución anterior no funcionará.

Puede anteponer cada línea de la salida cmdmayfailcon alguna línea de estado fijo (por ejemplo OK, ) y finalmente agregar una línea con el estado de salida de cmdmayfail. El código <()sería como:

 cmdmayfail | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"

Ejemplo:

$ (printf '%s\n' foo "bar baz"; exit 7) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"
OK
foo
OK
bar baz
7

Entonces su awkcódigo debería getline<fverificar si es OK. Si es así, la siguiente línea ( getline<fnuevamente) es de cmdmayfailseguro. Bucle para analizar todas las líneas hasta que no haya ninguna OKcuando lo espera. Entonces es el estado de salida.

Esto funcionará bien a menos que cmdmayfailpueda generar unlinea incompleta. Ejemplo:

$ (printf 'foo\nincomplete line'; exit 22) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"

Dependiendo de la implementación de sed, la herramienta puede

  1. ignorar la línea incompleta en absoluto, o
  2. procesarlo y agregar el carácter de nueva línea que falta, o
  3. procesarlo tal cual.

En efecto lo harás

  1. perder alguna parte de la salida, o
  2. no sé que la línea estaba incompleta, o
  3. obtenga la línea con el estado de salida adjunto.

En el caso de (3) printf '\n%s\n' "${PIPESTATUS[0]}"puede ayudar. Generará una línea vacía adicional si la última línea cmdmayfailestá completa; De esta manera su awkcódigo podrá saberlo.

Consideremos un caso en el que cmdmayfailse despidió por la fuerza la línea media. Es posible que entonces no desees analizar la línea incompleta. El problema es: para saber awksi el formulario de línea cmdmayfailestaba completo o no, es necesario probar la siguiente línea (de estado). Implementar una lógica útil para esto awkpuede resultar al menos inconveniente.


Es bueno detectar una línea incompleta lo antes posible,readen Bash puede hacer esto. La desventaja es readque es lento (y recuerde que las variables Bash no pueden almacenar caracteres nulos). Solución de ejemplo:

# define this helper function in the main shell
encerr () { ( eval "$@" ) | (while IFS= read -r line; do printf 'C\n%s\n' "$line"; done; [ -n "$line" ] && printf 'I\n%s\n' "$line") ; printf 'E\n%s\n' "${PIPESTATUS[0]}"; }
# this part you want to put in <()
encerr cmdmayfail

Luego necesitas decodificar el protocolo personalizado que contiene awk. Las líneas van en pares. (Consulte los ejemplos a continuación para comprender el protocolo de manera más intuitiva).

  1. Lea la primera línea de un par ( getline<f).
  2. Almacene la primera línea en una variable ( first=$0).
  3. Lea la segunda línea de un par ( getline<f).
  4. Analiza la primera línea ( $first).
    • Si es así, Centonces el segundo (actual $0) es una línea completa de cmdmayfail, puede analizarlo.
    • Si es así I, entonces la segunda es una línea incompleta de cmdmayfail, es posible que desee o no analizarla. Espere Een el próximo par.
    • Si es así, Eentonces el segundo es el estado de salida de cmdmayfail. No deberías esperar más parejas.
  5. Bucle.

Nota que usé eval "$@"dentro de la función. Lo que escriba después encerrserá evaluado por segunda vez, por lo que normalmente le gustaría ejecutar algo como

encerr 'cmd1 -opt foo'

o

encerr "cmd1 -opt foo"

o incluso

encerr 'cmd1 -opt foo | cmd2'

Básicamente, este es el formulario que utiliza para ejecutar comandos remotos ssh. Comparar:

ssh a@b 'cmd1 -opt foo | cmd2'

O puedes construir la función de esta manera:

encerr () { "$@" | …

y llámalo así:

encerr cmd1 -opt foo

Comparar con sudo:

sudo cmd1 -opt foo

Ejemplos (usando la función original con eval):

  • éxito con salida vacía

    $ encerr true
    E
    0
    
  • falla con salida vacía

    $ # I'm redirecting stderr so "command not found" doesn't obfuscate the example
    $ encerr nonexisting-command-foo1231234 2>/dev/null
    E
    127
    
  • éxito después de líneas completas

    $ encerr 'date; sleep 1; date'
    C
    Mon Sep 30 09:07:40 CEST 2019
    C
    Mon Sep 30 09:07:41 CEST 2019
    E
    0
    
  • falla después de líneas completas

    $ encerr 'printf "foo\nbar\n"; false'
    C
    foo
    C
    bar
    E
    1
    
  • éxito después de una línea incompleta

    $ encerr 'printf "foo bar\n89 baz"'
    C
    foo bar
    I
    89 baz
    E
    0
    
  • falla después de una línea incompleta

    $ encerr 'printf "\nThe first line was empty and this one was interru"; exit 33'
    C
    
    I
    The first line was empty and this one was interru
    E
    33
    

información relacionada