Tengo un binario (que no puedo modificar) y puedo hacer:
./binary < file
También 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 tubería. En los 3 casos, el contenido del archivo se entrega a la entrada estándar de binario (de diferentes maneras):
- bash lee el archivo y se lo da a stdin de binario
- bash lee líneas de stdin (hasta EOF) y se lo da a stdin de binario
- cat lee y pone las líneas de archivo en stdout, bash las redirige a stdin de binario
El binario no debería notar la diferencia entre esos 3 hasta donde yo lo entiendo. ¿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é de nuevo 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) .
fuente
isatty()
devuelva falso será un archivo buscable o mapeable ...cat
. Parece que no podría usarlo para combinar dos archivos, como es el uso previsto.Respuestas:
En
binary
'stdin' es el archivo abierto en modo de solo lectura. Tenga en cuenta quebash
no lee el archivo en absoluto, solo lo abre para leerlo en el descriptor de archivo 0 (stdin) del proceso en el que se ejecutabinary
.En:
Dependiendo del shell,
binary
el stdin del archivo será un archivo temporal eliminado (AT&T ksh, zsh, bash ...) que contienetest\n
lo que el shell o el extremo de lectura de una tubería han puesto allí (dash
,yash
; y el shell escribetest\n
en paralelo en el otro extremo de la tubería). En su caso, si está utilizandobash
, sería un archivo temporal.En:
Dependiendo del shell,
binary
la entrada estándar será el extremo de lectura de una tubería o un extremo de un par de zócalos donde la dirección de escritura se ha cerrado (ksh93) ycat
está escribiendo el contenidofile
en el otro extremo.Cuando stdin es un archivo normal (temporal o no), es buscable.
binary
puede ir al principio o al final, rebobinar, etc. También puede mapearlo, hacer algoioctl()s
como FIEMAP / FIBMAP (si se usa en<>
lugar de<
, podría truncar / perforar agujeros, etc.).Por otro lado, las tuberías y los pares de tomas son un medio de comunicación entre procesos, no hay mucho que
binary
pueda hacer ademásread
de los datos (aunque también hay algunas operaciones como algunas específicas de tuberíasioctl()
que podría hacer en ellos y no en los archivos normales) .La mayoría de las veces, es la capacidad que falta para
seek
que hace que las aplicaciones a fallar / quejan cuando se trabaja con tubos, pero podría ser cualquiera de las otras llamadas al sistema que son válidos en los archivos normales, pero no en los diferentes tipos de archivos (comommap()
,ftruncate()
,fallocate()
) . En Linux, también hay una gran diferencia en el comportamiento cuando se abre/dev/stdin
mientras el fd 0 está en una tubería o en un archivo normal.Existen 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
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 (regular en el primer caso, canalización en el segundo) se proporciona como un argumento de rutaunzip
y seunzip
abre por sí mismo (generalmente en fd que no sea 0) en lugar de heredar un fd ya abierto por el padre. No lee archivos zip de su stdin. stdin se usa principalmente para la interacción del usuario.Si ejecuta el
binary
suyo sin redireccionamiento en el indicador de un shell interactivo que se ejecuta en un emulador de terminal,binary
el stdin se heredará de su padre, el shell, que a su vez lo habrá heredado de su padre, el emulador de terminal y será un Dispositivo pty abierto en modo lectura + escritura (algo así como/dev/pts/n
).Esos dispositivos tampoco son buscables. Por lo tanto, si
binary
funciona bien al tomar la entrada desde el terminal, posiblemente el problema no sea buscar.Si ese 14 está destinado a ser un error (un código de error establecido por las llamadas fallidas del sistema), entonces en la mayoría de los sistemas, eso sería
EFAULT
( Dirección incorrecta ). Laread()
llamada del sistema fallará con ese error si se le pide que lea en una dirección de memoria que no se puede escribir. Eso sería independiente de si el fd lee los datos desde puntos a una tubería o archivo normal y generalmente indicaría un error 1 .binary
posiblemente determina el tipo de archivo abierto en su stdin (confstat()
) y se encuentra con un error cuando no es un archivo normal ni un dispositivo tty.Difícil de saber sin saber más sobre la aplicación. Ejecutarlo bajo
strace
(otruss
/tusc
equivalente en su sistema) podría ayudarnos a ver cuál es la llamada del sistema, si es que alguna falla aquí.1 El escenario previsto por Matthew Ife en un comentario a su pregunta suena muy plausible aquí. Citando a él:
fuente
./binary < file
es buscable!open
editado y se comporta igual que cualquier archivo que se hayaopen
editado. Resulta que se heredó de un proceso padre, pero eso no es tan raro.open("/proc/self/fd/0", O_RDWR)
funciona, incluso en archivos eliminados. Yo tonto: P.echo foo>foo; (sleep 0.5; ll -L /proc/self/fd/0; strace ./a.out; ll -L /proc/self/fd/0) < foo & sleep 0.1 && rm foo
se desvinculafoo
antes de que a.out se ejecute con su stdin redirigido desdefoo
.Aquí hay un programa de ejemplo simple que ilustra la respuesta de Stéphane Chazelas usando
lseek(2)
en su entrada:Pruebas:
Las tuberías no son buscables, y ese es un lugar donde un programa podría quejarse de las tuberías.
fuente
La tubería y la redirección son animales diferentes, por así decirlo. Cuando usa
here-doc
redirección (<<
) o redireccionamiento stdin,<
el texto no sale de la nada: en realidad va a un descriptor de archivo (o archivo temporal, si lo desea), y ahí es donde apuntará el stdin del binario.Específicamente, aquí hay un extracto del
bash's
código fuente, archivo redir.c (versión 4.3):Por lo tanto, dado que la redirección puede tratarse básicamente como archivos, los binarios pueden navegarlos, o
seek()
atravesar el archivo fácilmente, saltando a cualquier byte del archivo.Las tuberías, dado que son memorias intermedias de 64 KiB (al menos en Linux) con escrituras de 4096 bytes o menos garantizadas para ser atómicas, no son buscables, es decir, no puede navegar libremente por ellas, solo lea de forma secuencial. Una vez implementé el
tail
comando en Python. Se pueden buscar 29 millones de líneas de texto en microsegundos si se redirigen, pero si secat
editan a través de una tubería, bueno, no hay nada que se pueda hacer, por lo que todo debe leerse secuencialmente.Otra posibilidad es que el binario quiera abrir un archivo específicamente y no quiera recibir información de una tubería. Por lo general, se realiza a través de una
fstat()
llamada al sistema y se comprueba si la entrada proviene de unS_ISFIFO
tipo de archivo (que significa una tubería / tubería con nombre).Su binario específico, dado que no sabemos qué es, probablemente intente buscar, pero no puede buscar tuberías. Se recomienda que consulte su documentación para averiguar qué significa exactamente el código de error 14.
NOTA : Algunos shells, como el guión (Debian Almquist Shell, predeterminado
/bin/sh
en Ubuntu) implementan lahere-doc
redirección con tuberías internamente , por lo que puede que no se pueda buscar. El punto sigue siendo el mismo: las tuberías son secuenciales y no se pueden navegar fácilmente, y los intentos de hacerlo darán lugar a errores.fuente
dash
lo hacen. Esta respuesta explica el comportamiento observado con bash, pero ese comportamiento aparentemente no está garantizado en otros shells.dash
en mi sistema. No estaba al tanto de eso anteriormente. Gracias por señalarfstat()
usarías en stdin para verificar si es una tubería.stat
toma un nombre de ruta. Pero realmente, solo intentarlolseek
es probablemente la forma más sensata de determinar si un fd es buscable después de que ya está abierto.La principal diferencia está en el manejo de errores.
En el siguiente caso se informa el error
En el siguiente caso no se informa el error.
Con bash, aún puedes usar PIPESTATUS:
Pero está disponible solo inmediatamente después de la ejecución del comando:
Hay otra diferencia, cuando usamos funciones de shell en lugar de binarios. En
bash
, las funciones que forman parte de una tubería se ejecutan en subcapas (excepto el último componente de la tubería si lalastpipe
opción está habilitada ybash
no es interactiva), por lo que el cambio de variables no tiene efectos en el shell principal:fuente
>
se realiza mediante el shell, pero con pipe se realiza mediante un comando que produce texto. OKAY. Pero en esta pregunta específica, OP está utilizando un archivo existente, por lo que ese no es el problema, y claramente el error producido es por el binario.