$ 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, awk
no 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 awk
cuenta de este error, podría usar el código anterior enviando una secuencia de control y alguna información de error.
Dado que file
ya conoce muchos tipos de archivos, ¿existe una secuencia de control razonable que pueda usarse para esta aplicación de modo que awk
no maltrate la secuencia de control de errores como si fuera un archivo legítimo? Gracias.
Respuesta1
Si se commandmayfail
trata 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 awk
comprender 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 commandmayfail
es file
, como sugiere la pregunta, puede ser suficiente usar una cadena sin :
.
Respuesta2
Si el resultado de cmdmayfail
es 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<f
Consecutive getline<f
leerá la salida real de cmdmayfail
.
Limitaciones:
- Las variables en Bash no pueden almacenar caracteres nulos.
$()
eliminará todos los caracteres de nueva línea finales; luegoprintf
agregará 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 cmdmayfail
del BEGIN
bloque puede indicar que espera cmdmayfail
terminar antes de tiempo. Quizás esta solución sea suficiente.
En general, cmdmayfail
puede 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 cmdmayfail
con 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 awk
código debería getline<f
verificar si es OK
. Si es así, la siguiente línea ( getline<f
nuevamente) es de cmdmayfail
seguro. Bucle para analizar todas las líneas hasta que no haya ninguna OK
cuando lo espera. Entonces es el estado de salida.
Esto funcionará bien a menos que cmdmayfail
pueda 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
- ignorar la línea incompleta en absoluto, o
- procesarlo y agregar el carácter de nueva línea que falta, o
- procesarlo tal cual.
En efecto lo harás
- perder alguna parte de la salida, o
- no sé que la línea estaba incompleta, o
- 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 cmdmayfail
está completa; De esta manera su awk
código podrá saberlo.
Consideremos un caso en el que cmdmayfail
se despidió por la fuerza la línea media. Es posible que entonces no desees analizar la línea incompleta. El problema es: para saber awk
si el formulario de línea cmdmayfail
estaba completo o no, es necesario probar la siguiente línea (de estado). Implementar una lógica útil para esto awk
puede resultar al menos inconveniente.
Es bueno detectar una línea incompleta lo antes posible,read
en Bash puede hacer esto. La desventaja es read
que 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).
- Lea la primera línea de un par (
getline<f
). - Almacene la primera línea en una variable (
first=$0
). - Lea la segunda línea de un par (
getline<f
). - Analiza la primera línea (
$first
).- Si es así,
C
entonces el segundo (actual$0
) es una línea completa decmdmayfail
, puede analizarlo. - Si es así
I
, entonces la segunda es una línea incompleta decmdmayfail
, es posible que desee o no analizarla. EspereE
en el próximo par. - Si es así,
E
entonces el segundo es el estado de salida decmdmayfail
. No deberías esperar más parejas.
- Si es así,
- Bucle.
Nota que usé eval "$@"
dentro de la función. Lo que escriba después encerr
será 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