Pregunta:
Tengo un binario (que no puedo modificar) y puedo hacer:
./binary < file
Yo tambien puedo hacer:
./binary << EOF
> "line 1 of file"
> "line 2 of file"
...
> "last line of file"
> EOF
Pero
cat file | ./binary
me da un error. No sé por qué no funciona con una pipa. En los 3 casos, el contenido del archivo se da a la entrada estándar de binario (de diferentes maneras):
- bash lee el archivo y lo da a stdin de binario
- bash lee líneas desde stdin (hasta EOF) y lo da a stdin del binario
- cat lee y coloca las líneas del archivo en stdout, bash las redirige a stdin del binario
El binario no debería notar la diferencia entre esos 3 hasta donde yo lo entendí. ¿Alguien puede explicar por qué el tercer caso no funciona?
Por cierto: el error dado por el binario es:
20170116/125624.689 – U3000011 No se pudo leer el archivo de script '', código de error '14'.
Pero mi pregunta principal es, ¿cómo hay una diferencia para cualquier programa con esas 3 opciones?
Aquí hay algunos detalles adicionales: Lo intenté nuevamente con strace y, de hecho, hubo algunos errores ESPIPE (búsqueda ilegal) de lseek seguido de EFAULT (dirección incorrecta) de la lectura justo antes del mensaje de error.
El binario que intenté controlar con un script ruby (sin usar archivos temporales) es parte del callapi de Automic (UC4) .
Respuesta:
En
./binary < file
stdin de binary
es el archivo que se abre en modo de solo lectura. Tenga en cuenta que bash
no lee el archivo en absoluto, solo lo abre para leer en el descriptor de archivo 0 (stdin) del proceso en el que ejecuta el binary
.
En:
./binary << EOF
test
EOF
Dependiendo del shell, el stdin de binary
será un archivo temporal eliminado (AT&T ksh, zsh, bash …) que contiene test\n
como lo pone el shell o el extremo de lectura de una tubería ( dash
, yash
; y el shell escribe test\n
en paralelo en el otro extremo de la tubería). En su caso, si está usando bash
, sería un archivo temporal.
En:
cat file | ./binary
Dependiendo del shell, el stdin de binary
será el extremo de lectura de una tubería o un extremo de un par de conectores donde la dirección de escritura se ha cerrado (ksh93) y cat
está escribiendo el contenido del file
en el otro extremo.
Cuando stdin es un archivo normal (temporal o no), se puede buscar. binary
puede ir al principio o al final, rebobinar, etc. También puede mmap, hacer algunos ioctl()s
como FIEMAP / FIBMAP (si usa <>
lugar de <
, podría truncarlo / perforarlo, etc.).
Por otro lado, las tuberías y los pares de conectores son un medio de comunicación entre procesos, no hay mucho que pueda hacer el binary
además de read
los datos (aunque también hay algunas operaciones como algunos ioctl()
específicos de tubería que podría hacer en ellos y no en archivos normales).
La mayoría de las veces, es la falta de capacidad de seek
que hace que las aplicaciones fallen / se quejen cuando se trabaja con tuberías, pero podría ser cualquiera de las otras llamadas al sistema que son válidas en archivos normales pero no en diferentes tipos de archivos (como mmap()
, ftruncate()
, fallocate()
). En Linux, también hay una gran diferencia en el comportamiento cuando abre /dev/stdin
mientras el fd 0 está en una tubería o en un archivo normal.
Hay muchos comandos que solo pueden tratar con archivos buscables , pero cuando ese es el caso, generalmente no es para los archivos abiertos en su stdin.
$ unzip -l file.zip
Archive: file.zip
Length Date Time Name
--------- ---------- ----- ----
11 2016-12-21 14:43 file
--------- -------
11 1 file
$ unzip -l <(cat file.zip)
# more or less the same as cat file.zip | unzip -l /dev/stdin
Archive: /proc/self/fd/11
End-of-central-directory signature not found. Either this file is not
a zipfile, or it constitutes one disk of a multi-part archive. In the
latter case the central directory and zipfile comment will be found on
the last disk(s) of this archive.
unzip: cannot find zipfile directory in one of /proc/self/fd/11 or
/proc/self/fd/11.zip, and cannot find /proc/self/fd/11.ZIP, period.
unzip
necesita leer el índice almacenado al final del archivo, y luego buscar dentro del archivo para leer los miembros del archivo. Pero aquí, el archivo (normal en el primer caso, pipe en el segundo) se proporciona como un argumento de ruta para unzip
, y unzip
abre por sí mismo (generalmente en fd distinto de 0) en lugar de heredar un fd ya abierto por la persona que llama. No lee archivos zip de su stdin. stdin se utiliza principalmente para la interacción del usuario.
Si ejecuta ese binary
sin redireccionamiento en el indicador de un shell interactivo que se ejecuta en un emulador de terminal, entonces el stdin del binary
se heredará de su llamador, el shell, que a su vez lo habrá heredado de su llamador, el emulador de terminal y lo hará. ser un dispositivo pty abierto en modo lectura + escritura (algo así como /dev/pts/n
).
Esos dispositivos tampoco se pueden buscar. Por lo tanto, si el binary
funciona bien al recibir datos del terminal, posiblemente el problema no se trate de buscar.
Si ese 14 está destinado a ser un errno (un código de error establecido por llamadas al sistema EFAULT
), entonces en la mayoría de los sistemas, sería EFAULT
( dirección incorrecta ). La llamada al sistema read()
fallaría con ese error si se le pidiera leer en una dirección de memoria que no se puede escribir. Eso sería independiente de si el fd leerá los datos de los puntos a una tubería o un archivo normal y generalmente indicaría un error 1 .
binary
posiblemente determina el tipo de archivo abierto en su stdin (con fstat()
) y se encuentra con un error cuando no es un archivo normal ni un dispositivo tty.
Difícil de decir sin saber más sobre la aplicación. Ejecutarlo bajo strace
(o el equivalente de truss
/ tusc
en su sistema) podría ayudarnos a ver cuál es la llamada al sistema, si hay alguna que esté fallando aquí.
1 El escenario previsto por Matthew Ife en un comentario a su pregunta suena muy plausible aquí. Citando a él:
Sospecho que está buscando hasta el final del archivo para obtener un tamaño de búfer para leer los datos, manejando mal el hecho de que la búsqueda no funciona e intentando asignar un tamaño negativo (no manejando un malloc malo). Pasar el búfer para leer qué fallas dado el búfer no es válido.