¿Cuál es la diferencia entre "cat file | ./binary ”y“ ./binary <file ”?

102

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):

  1. bash lee el archivo y se lo da a stdin de binario
  2. bash lee líneas de stdin (hasta EOF) y se lo da a stdin de binario
  3. 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) .

Boris
fuente
25
Genial, hay un detector UUOC incrustado en su binario. Lo quiero.
xhienne
44
¿Qué sistema operativo es (para que podamos decir qué es 14 si está destinado a ser un error)?
Stéphane Chazelas
66
Aunque es posible que un programa reaccione de esta manera, sería un error que lo hiciera. Todos los programas no locos que esperan cualquier entrada de stdin deben funcionar cuando stdin es un tty, y si puede funcionar tanto con un tty como con un archivo, hay pocas razones para no admitir tuberías también. Probablemente el autor del programa tuvo una hemorragia temporal y, aunque todo lo que isatty()devuelva falso será un archivo buscable o mapeable ...
Henning Makholm
99
El código de error 14 significa EFAULT. En una lectura que ocurre si el búfer que ha declarado no es válido. Arreglaría el programa pero sospecho que está buscando al final del archivo 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é fallos dado el búfer no es válido
Matthew Ife el
3
@xhienne No, tiene un elemento preventivo cat. Parece que no podría usarlo para combinar dos archivos, como es el uso previsto.
jpmc26

Respuestas:

150

En

./binary < file

binary'stdin' es el archivo abierto en modo de solo lectura. Tenga en cuenta que bashno lee el archivo en absoluto, solo lo abre para leerlo en el descriptor de archivo 0 (stdin) del proceso en el que se ejecuta binary.

En:

./binary << EOF
test
EOF

Dependiendo del shell, binaryel stdin del archivo será un archivo temporal eliminado (AT&T ksh, zsh, bash ...) que contiene test\nlo que el shell o el extremo de lectura de una tubería han puesto allí ( dash, yash; y el shell escribe test\nen paralelo en el otro extremo de la tubería). En su caso, si está utilizando bash, sería un archivo temporal.

En:

cat file | ./binary

Dependiendo del shell, binaryla 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) y catestá escribiendo el contenido fileen el otro extremo.

Cuando stdin es un archivo normal (temporal o no), es buscable. binarypuede ir al principio o al final, rebobinar, etc. También puede mapearlo, hacer algo ioctl()scomo 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 binarypueda hacer además readde los datos (aunque también hay algunas operaciones como algunas específicas de tuberías ioctl()que podría hacer en ellos y no en los archivos normales) .

La mayoría de las veces, es la capacidad que falta para seekque 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 (como mmap(), ftruncate(), fallocate()) . En Linux, también hay una gran diferencia en el comportamiento cuando se abre /dev/stdinmientras 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 -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.

unzipnecesita 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 ruta unzipy se unzipabre 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 binarysuyo sin redireccionamiento en el indicador de un shell interactivo que se ejecuta en un emulador de terminal, binaryel 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 binaryfunciona 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 ). La read()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 .

binaryposiblemente 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 saber sin saber más sobre la aplicación. Ejecutarlo bajo strace(o truss/ tuscequivalente 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:

Sospecho que está buscando al final del archivo 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é fallos dado el búfer no es válido.

Stéphane Chazelas
fuente
14
Muy interesante ... ¡esta es la primera vez que escucho que la entrada estándar redirigida en el estilo de ./binary < filees buscable!
David Z
2
@DavidZ es un archivo que se ha openeditado y se comporta igual que cualquier archivo que se haya openeditado. Resulta que se heredó de un proceso padre, pero eso no es tan raro.
hobbs
3
Si el sistema contiene strace o una herramienta similar, podría usarse para verificar qué llamada al sistema falla el binario.
pabouk
2
"También puede truncarlo, mapearlo, perforarlo, etc." - Bueno no. El archivo está abierto en modo de solo lectura. El programa tendría que abrirlo en modo de escritura para hacer eso. Pero no puede abrirlo en modo de escritura, porque no hay una interfaz para hacer eso directamente, ni tampoco hay una interfaz para encontrar "la" entrada del directorio que corresponde a un archivo abierto (¿qué pasa si hay dos dentries tales o cero?) . Tendría que registrar el archivo y luego escanear el sistema de archivos en busca de un objeto con el mismo número de inodo. Eso sería excesivamente lento.
Kevin
1
@ StéphaneChazelas: oh cierto, 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 foose desvincula fooantes de que a.out se ejecute con su stdin redirigido desde foo.
Peter Cordes
46

Aquí hay un programa de ejemplo simple que ilustra la respuesta de Stéphane Chazelas usando lseek(2)en su entrada:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    int c;
    off_t off;
    off = lseek(0, 10, SEEK_SET);
    if (off == -1)
    {
        perror("Error");
        return -1;
    }
    c = getchar();
    printf("%c\n", c);
}

Pruebas:

$ make seek
cc     seek.c   -o seek
$ cat foo
abcdefghijklmnopqrstuwxyz
$ ./seek < foo
k
$ ./seek <<EOF
> abcdefghijklmnopqrstuvwxyz
> EOF
k
$ cat foo | ./seek
Error: Illegal seek

Las tuberías no son buscables, y ese es un lugar donde un programa podría quejarse de las tuberías.

muru
fuente
21

La tubería y la redirección son animales diferentes, por así decirlo. Cuando usa here-docredirecció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'scódigo fuente, archivo redir.c (versión 4.3):

/* Create a temporary file holding the text of the here document pointed to
   by REDIRECTEE, and return a file descriptor open for reading to the temp
   file.  Return -1 on any error, and make sure errno is set appropriately. */
static int
here_document_to_fd (redirectee, ri)

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 tailcomando en Python. Se pueden buscar 29 millones de líneas de texto en microsegundos si se redirigen, pero si se cateditan 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 un S_ISFIFOtipo 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/shen Ubuntu) implementan la here-docredirecció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.

Sergiy Kolodyazhnyy
fuente
La respuesta de Stephane dice que los documentos aquí se pueden implementar con tuberías, y que algunos shells comunes como dashlo hacen. Esta respuesta explica el comportamiento observado con bash, pero ese comportamiento aparentemente no está garantizado en otros shells.
Peter Cordes
@PeterCordes es absolutamente así, y acabo de verificarlo dashen mi sistema. No estaba al tanto de eso anteriormente. Gracias por señalar
Sergiy Kolodyazhnyy
Otro comentario: fstat()usarías en stdin para verificar si es una tubería. stattoma un nombre de ruta. Pero realmente, solo intentarlo lseekes probablemente la forma más sensata de determinar si un fd es buscable después de que ya está abierto.
Peter Cordes
5

La principal diferencia está en el manejo de errores.

En el siguiente caso se informa el error

$ /bin/cat < z.txt
-bash: z.txt: No such file or directory
$ echo $?
1

En el siguiente caso no se informa el error.

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0

Con bash, aún puedes usar PIPESTATUS:

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo ${PIPESTATUS[0]}
1

Pero está disponible solo inmediatamente después de la ejecución del comando:

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0
$ echo ${PIPESTATUS[0]}
0
# oops !

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 la lastpipeopción está habilitada y bashno es interactiva), por lo que el cambio de variables no tiene efectos en el shell principal:

$ a=a
$ b=b
$ x(){ a=x;}
$ y(){ b=y;}

$ echo $a $b
a b

$ x | y
$ echo $a $b
a b

$ cat t.txt | y
$ echo $a $b
a b

$ x | cat
$ echo $a $b
a b

$ x < t.txt
$ y < t.txt
$ echo $a $b
x y
Vouze
fuente
44
Entonces, está mostrando que el manejo de errores >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.
Sergiy Kolodyazhnyy
1
Si bien en su mayoría no viene al caso, esta respuesta tiene cierta relevancia para estas preguntas y respuestas en el caso general y es en su mayoría correcta, por lo que no creo que merezca esos votos negativos.
Stéphane Chazelas
@Serg: cuando usa shell como línea de comando, esto no es importante. Pero en los scripts, el manejo de errores puede ser muy importante.
Vouze