
Recientemente me topé con un problema sorprendente con un error no válido /dev/stderr
en un cygwin actualizado que también está presente en una instalación de Debian bien madura . (Editar: a diferencia de lo que pensé originalmente, mi sistema Debian no expone este error sino que simplemente produce el resultado deseado. Ahora debo suponer que se trata de un error de cygwin).
Antecedentes: estoy usando herramientas que producen miles de líneas de salida (específicamente: sistemas de control de versiones en un sistema de producción grande). Los estoy ejecutando controlados por script y, opcionalmente, quería redirigir la salida ruidosa de la herramienta a un archivo de registro. Una solución simple parecía redirigir siempre su salida (stderr y stdout) a un destino del sistema de archivos que estaba almacenado en una variable de entorno. Si se desea enviar al terminal (o algún destino controlado por el usuario), el destino DBG_STDERR
sería simplemente "/dev/stderr", de lo contrario, algún nombre de archivo temporal. Una línea típica de ejecución de herramientas se parecería entonces a noisy_command >> "$DBG_STDERR" 2>&1
.
Esto funciona bien a menos que canalice la salida del script. Aquí hay una reproducción mínima:
$ uname -a
CYGWIN_NT-6.1-WOW xxxxxxx 2.8.1(0.312/5/3) 2017-07-03 14:06 i686 Cygwin
$ bash --version
GNU bash, version 4.4.12(3)-release (i686-pc-cygwin)
$ cat say-something.sh
#!/bin/sh
echo something > /dev/stderr
$ (x=$(./say-something.sh 2> /dev/stderr)) 2>&1 |cat
./say-something.sh: line 2: /dev/stderr: No such file or directory
$ (x=$(./say-something.sh 2> /dev/stderr)) 2>&1
something
$ (x=$(./say-something.sh 2> /dev/stderr)) |cat
something
$ x=$(./say-something.sh 2> /dev/stderr) 2>&1 |cat
something
Por supuesto, todas las redirecciones y shells anidados parecen graciosos fuera de contexto. El shell adicional es necesario porque otro script llamaría a say-something.sh. La redirección redundante de fd 2 a stderr es el "interruptor" para facilitar la redirección opcional a un archivo (/dev/stderr, o una ruta diferente, es en realidad el contenido configurable de una variable).
Parece que todos los componentes de este oleoducto son necesarios, como lo demuestran los experimentos posteriores al ejemplo fallido: todos tienen éxito.
- Necesitamos la tubería final de stdout.
- Necesitamos que la persona que llama copie stderr a stdout
- Necesitamos la capa exterior alrededor de la sustitución del comando.
Respuesta1
En realidad, el nombre /dev/stderr
es válido cuando se redirige a una tubería. Lo que quizás no sea posible es abrir el objetivo final /dev/stderr
directamente. Sólo ve:
$ (echo Testing testing > /dev/stderr) |& cat
Testing testing
La tubería creada por |
o |&
suele ser untubería anónima; el nombre mostradono corresponde a un objeto en el sistema de archivos. Para ver una ilustración, puedes probar algo simple como:
$ ls -la /dev/fd/ |& cat
total 0
dr-x------ 2 alexp alexp 0 Jul 6 18:23 .
dr-xr-xr-x 9 alexp alexp 0 Jul 6 18:23 ..
lrwx------ 1 alexp alexp 64 Jul 6 18:23 0 -> /dev/pts/4
l-wx------ 1 alexp alexp 64 Jul 6 18:23 1 -> pipe:[1058859]
l-wx------ 1 alexp alexp 64 Jul 6 18:23 2 -> pipe:[1058859]
lr-x------ 1 alexp alexp 64 Jul 6 18:23 3 -> /proc/4335/fd
Es muy inusual intentar abrir el objetivo (final) de /dev/stderr
; el nombre /dev/stderr
se proporciona en ordenpara evitarmolestarse en descubrir el objetivo real.
Respuesta2
Creo que el problema es el proceso de lectura de línea que se genera. Obtiene su propia tubería para la redirección que se cierra cuando el proceso se detiene (el pid que obtiene no es el del shell sino el del proceso readlink). La tubería deja de ser válida cuando el proceso sale. Intente utilizar fifos/tuberías con nombre.