Qual è la differenza tra “file cat | ./binary "e" ./binary <file "?


102

Ho un file binario (che non posso modificare) e posso fare:

./binary < file

Posso anche fare:

./binary << EOF
> "line 1 of file"
> "line 2 of file"
...
> "last line of file"
> EOF

Ma

cat file | ./binary

mi dà un errore. Non so perché non funziona con una pipa. In tutti e 3 i casi il contenuto del file è dato allo standard input del binario (in diversi modi):

  1. bash legge il file e lo dà allo stdin del binario
  2. bash legge le righe da stdin (fino a EOF) e le dà a stdin di binario
  3. cat legge e mette le linee di file su stdout, bash le reindirizza su stdin di binario

Il binario non dovrebbe notare la differenza tra quei 3 per quanto ho capito. Qualcuno può spiegare perché il terzo caso non funziona?

A proposito: l'errore dato dal binario è:

20170116 / 125624.689 - U3000011 Impossibile leggere il file di script '', codice errore '14'.

Ma la mia domanda principale è: in che modo c'è una differenza per qualsiasi programma con quelle 3 opzioni.

Ecco alcuni ulteriori dettagli: l'ho provato di nuovo con strace e in effetti c'erano alcuni errori ESPIPE (ricerca illegale) da lseek seguito da EFAULT (indirizzo errato) da leggere subito prima del messaggio di errore.

Il binario che ho provato a controllare con uno script ruby ​​(senza usare file temporanei) fa parte dei callapi di Automic (UC4) .


25
Bene , c'è un rilevatore UUOC incorporato nel tuo binario. Lo voglio.
Xhienne,

4
Che sistema operativo è (quindi possiamo dire cosa è 14 se si intende che sia un errore)?
Stéphane Chazelas,

6
Anche se è possibile che un programma reagisca in questo modo, sarebbe un programma incredibilmente difettoso. Ogni programma non pazzo che si aspetta qualsiasi input da stdin deve funzionare quando stdin è un tty e se può funzionare sia con un tty che con un file, ci sono poche ragioni per non supportare anche le pipe. Probabilmente l'autore del programma ha avuto un'emorragia temporanea e anche se tutto ciò che isatty()restituisce falso sarà un file ricercabile o mmappable ...
Henning Makholm

9
Il codice di errore 14 sta per EFAULT. In una lettura che si verifica se il buffer dichiarato non è valido. Stringerei il programma ma sospetto che stia cercando la fine del file per ottenere una dimensione del buffer per la lettura dei dati, gestendo male il fatto che la ricerca non funziona e tentando di allocare una dimensione negativa (non gestendo un malloc difettoso) . Passare il buffer per leggere quali errori dati al buffer non sono validi.
Matthew Ife,

3
@xhienne No, ha un dispositivo di catprevenzione. Sembra che non sia possibile utilizzarlo per combinare due file, come previsto.
jpmc26,

Risposte:


150

Nel

./binary < file

binarystdin è il file aperto in modalità sola lettura. Nota che bashnon legge affatto il file, lo apre solo per la lettura sul descrittore di file 0 (stdin) del processo in cui viene eseguito binary.

Nel:

./binary << EOF
test
EOF

A seconda della shell, lo binarystdin sarà o un file temporaneo cancellato (AT&T ksh, zsh, bash ...) che contiene test\ncome inserito lì dalla shell o l'estremità di lettura di una pipe ( dash, yash; e la shell scrive test\nin parallelo all'altra estremità del tubo). Nel tuo caso, se lo stai usando bash, sarebbe un file temporaneo.

Nel:

cat file | ./binary

A seconda della shell, lo binarystdin sarà o la fine della lettura di una pipe, o un'estremità di una coppia di socket in cui la direzione di scrittura è stata chiusa (ksh93) e catsta scrivendo il contenuto filedell'altra estremità.

Quando stdin è un file normale (temporaneo o no), è ricercabile. binarypuò andare all'inizio o alla fine, riavvolgere, ecc. Può anche capovolgerlo, fare qualcosa ioctl()scome FIEMAP / FIBMAP (se usato al <>posto di <, potrebbe troncare / fare buchi in esso, ecc.).

le pipe e le coppie di socket invece sono un mezzo di comunicazione tra processi, non c'è molto da binaryfare oltre readai dati (anche se ci sono anche alcune operazioni come alcune specifiche di pipe ioctl()che potrebbero fare su di esse e non su file regolari) .

La maggior parte delle volte, è la capacità mancante seekche provoca le applicazioni a fallire / si lamentano quando si lavora con i tubi, ma potrebbe essere una qualsiasi delle altre chiamate di sistema che sono validi in file regolari, ma non su diversi tipi di file (come mmap(), ftruncate(), fallocate()) . Su Linux, c'è anche una grande differenza nel comportamento quando si apre /dev/stdinmentre fd 0 è su una pipe o su un file normale.

Esistono molti comandi là fuori che possono occuparsi solo di file ricercabili , ma in questo caso, generalmente non è per i file aperti sul loro 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.

unzipdeve leggere l'indice memorizzato alla fine del file, quindi cercare all'interno del file per leggere i membri dell'archivio. Ma qui, il file (regolare nel primo caso, pipe nel secondo) viene fornito come argomento path unzipe lo unzipapre da solo (in genere su fd diverso da 0) invece di ereditare un fd già aperto dal genitore. Non legge i file zip dal suo stdin. stdin è utilizzato principalmente per l'interazione dell'utente.

Se esegui quello binarytuo senza reindirizzamento al prompt di una shell interattiva in esecuzione in un emulatore di terminale, allora lo binarystdin verrà ereditato dalla shell madre, che a sua volta lo avrà ereditato dall'emulatore di terminale e sarà un dispositivo pty aperto in modalità lettura + scrittura (qualcosa del genere /dev/pts/n).

Neanche questi dispositivi sono cercabili. Quindi, se binaryfunziona correttamente quando si riceve l'input dal terminale, probabilmente il problema non riguarda la ricerca.

Se quel 14 è pensato per essere un errno (un codice di errore impostato da chiamate di sistema non riuscite), allora sulla maggior parte dei sistemi sarebbe EFAULT( indirizzo errato ). La read()chiamata di sistema fallirebbe con quell'errore se si chiedesse di leggere in un indirizzo di memoria non scrivibile. Ciò sarebbe indipendente dal fatto che il fd leggesse i dati da punti a una pipe o un file normale e indicasse generalmente un bug 1 .

binaryprobabilmente determina il tipo di file aperto sul suo stdin (con fstat()) e si imbatte in un bug quando non è né un file normale né un dispositivo tty.

Difficile dirlo senza sapere di più sull'applicazione. Eseguirlo con strace(o truss/ tuscequivalente sul tuo sistema) potrebbe aiutarci a vedere qual è la chiamata di sistema se qualcuno non riesce qui.


1 Lo scenario previsto da Matthew Ife in un commento alla tua domanda suona molto plausibile qui. Citandolo:

Ho il sospetto che stia cercando la fine del file per ottenere una dimensione del buffer per leggere i dati, gestire male il fatto che la ricerca non funziona e tentare di allocare una dimensione negativa (non gestire un malloc non valido). Passare il buffer per leggere quali errori dati al buffer non sono validi.


14
Molto interessante ... questo è il primo che ho sentito che ./binary < fileè ricercabile l' input standard reindirizzato nello stile di !
David Z,

2
@DavidZ è un file che è stato editato opene si comporta come qualsiasi altro file che è stato openeditato. Capita solo di essere stato ereditato da un processo genitore, ma non è così raro.
Hobbs

3
Se il sistema contiene strace o uno strumento simile, potrebbe essere utilizzato per verificare su quale chiamata di sistema il binario ha esito negativo.
pabouk,

2
"Può anche troncarlo, aprirlo, praticare dei fori ecc." - Beh no. Il file è aperto in modalità di sola lettura. Per farlo, il programma dovrebbe aprirlo in modalità di scrittura. Ma non può aprirlo in modalità di scrittura, perché non esiste un'interfaccia per farlo direttamente, né esiste un'interfaccia per trovare "la" voce della directory che corrisponde a un file aperto (e se ci fossero due di questi dentries o zero?) . Dovrebbe stat il file e quindi scansionare il filesystem per un oggetto con lo stesso numero di inode. Sarebbe eccessivamente lento.
Kevin,

1
@ StéphaneChazelas: oh giusto, open("/proc/self/fd/0", O_RDWR)funziona, anche su file cancellati. Silly me: 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 fooscollega fooprima che a.out venga eseguito con lo stdin reindirizzato da foo.
Peter Cordes,

46

Ecco un semplice programma di esempio che illustra la risposta di Stéphane Chazelas usando lseek(2)sul suo input:

#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);
}

test:

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

Le pipe non sono ricercabili, ed è qui che un programma potrebbe lamentarsi delle pipe.


21

La pipe e il reindirizzamento sono animali diversi, per così dire. Quando usi il here-docreindirizzamento ( <<) o il reindirizzamento di stdin, < il testo non arriva dal nulla - in realtà va in un descrittore di file (o in un file temporaneo, se vuoi), ed è lì che punta lo stdin del binario.

In particolare, ecco un estratto dal bash'scodice sorgente, file redir.c (versione 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)

Quindi, dal momento che il reindirizzamento può essere sostanzialmente trattato come file, i binari possono spostarli o seek()attraverso il file facilmente, passando a qualsiasi byte del file.

Le pipe, poiché sono buffer di 64 KiB (almeno su Linux) con scritture di 4096 byte o meno garantite come atomiche, non sono ricercabili, cioè non è possibile spostarle liberamente - leggere solo in sequenza. Una volta ho implementato il tailcomando in Python. 29 milioni di righe di testo possono essere ricercate in microsecondi se reindirizzate, ma se vengono cateseguite tramite pipe, beh, non c'è nulla che si possa fare, quindi tutto deve essere letto in sequenza.

Un'altra possibilità è che il binario potrebbe voler aprire un file in modo specifico e non desidera ricevere input da una pipe. Di solito viene eseguito tramite fstat()chiamata di sistema e verifica se l'input proviene da un S_ISFIFOtipo di file (che indica una pipe / named pipe).

Il tuo binario specifico, dal momento che non sappiamo cosa sia, probabilmente tenta di cercare, ma non può cercare pipe. Si consiglia di consultare la documentazione per scoprire cosa significa esattamente il codice di errore 14.

NOTA : Alcune shell, come dash (Debian Almquist Shell, impostazione predefinita /bin/shsu Ubuntu) implementano il here-docreindirizzamento con pipe internamente , quindi potrebbero non essere ricercabili. Il punto rimane lo stesso: i tubi sono sequenziali e non possono essere navigati facilmente e i tentativi di farlo comporteranno errori.


La risposta di Stephane afferma che qui i documenti possono essere implementati con pipe e che alcune shell comuni come questa lo dashfanno. Questa risposta spiega il comportamento osservato con bash, ma apparentemente quel comportamento non è garantito su altre shell.
Peter Cordes,

@PeterCordes è assolutamente così, e l'ho appena verificato dashsul mio sistema. Non ne ero a conoscenza in precedenza. Grazie per
averlo

Un altro commento: fstat()useresti su stdin per verificare se è una pipe. stataccetta un percorso. Ma davvero, solo tentare di farlo lseekè probabilmente il modo più sano per determinare se un fd è ricercabile dopo che è già aperto.
Peter Cordes,

5

La differenza principale sta nella gestione degli errori.

Nel caso seguente viene segnalato l'errore

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

Nel seguente caso l'errore non viene segnalato.

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

Con bash, puoi ancora usare PIPESTATUS:

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

Ma è disponibile solo immediatamente dopo l'esecuzione del comando:

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

C'è un'altra differenza, quando usiamo le funzioni shell invece dei binari. In bash, le funzioni che fanno parte di una pipeline vengono eseguite in sotto-shell (ad eccezione dell'ultimo componente della pipeline se l' lastpipeopzione è abilitata e bashnon interattiva), quindi la modifica delle variabili non ha effetti nella shell padre:

$ 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

4
Quindi, stai mostrando che la gestione degli errori >è fatta dalla shell, ma con pipe è fatta dal comando che produce testo. OK. Ma in questa domanda specifica, OP utilizza un file esistente, quindi non è questo il problema, e chiaramente l'errore prodotto è binario.
Sergiy Kolodyazhnyy,

1
Sebbene sia per lo più al di fuori del punto, questa risposta ha una certa rilevanza per queste domande e risposte nel caso generale ed è per lo più corretta, quindi non penso che meriti quei voti negativi.
Stéphane Chazelas,

@Serg: quando usi shell come riga di comando, questo non è importante. Ma negli script, la gestione degli errori può essere molto importante.
Vouze,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.