Come verificare se una pipe è vuota ed eseguire un comando sui dati se non lo è?


42

Ho reindirizzato una riga nello script bash e voglio verificare se la pipe ha dati, prima di inviarli a un programma.

Cercando ho scoperto test -t 0ma non funziona qui. Restituisce sempre falso. Quindi, come essere sicuri che la pipe abbia dati?

Esempio:

echo "string" | [ -t 0 ] && echo "empty" || echo "fill"

Produzione: fill

echo "string" | tail -n+2 | [ -t 0 ] && echo "empty" || echo "fill"

Produzione: fill

A differenza del modo standard / canonico per verificare se la pipeline precedente ha prodotto output? l'input deve essere preservato per passarlo al programma. Questo in generale Come convogliare l'output da un processo a un altro ma eseguirlo solo se il primo ha output? che si concentra sull'invio di e-mail.


Risposte:


37

Non c'è modo di sbirciare il contenuto di una pipe usando le utilità di shell comunemente disponibili, né c'è un modo per leggere un personaggio nella pipe e poi rimetterlo. L'unico modo per sapere che una pipe ha dati è leggere un byte, quindi devi portarlo a destinazione.

Quindi fai proprio questo: leggi un byte; se rilevi una fine del file, fai quello che vuoi fare quando l'input è vuoto; se si legge un byte, quindi fork ciò che si desidera fare quando l'input non è vuoto, reindirizzare quel byte e reindirizzare il resto dei dati.

first_byte=$(dd bs=1 count=1 2>/dev/null | od -t o1 -A n | tr -dc 0-9)
if [ -z "$first_byte" ]; then
  # stuff to do if the input is empty
else
  {
    printf "\\$first_byte"
    cat
  } | {
    # stuff to do if the input is not empty
  }      
fi

L' ifneutility dai moreutils di Joey Hess esegue un comando se il suo input non è vuoto. Di solito non è installato di default, ma dovrebbe essere disponibile o facile da costruire sulla maggior parte delle varianti unix. Se l'input è vuoto, ifnenon fa nulla e restituisce lo stato 0, che non può essere distinto dal comando eseguito correttamente. Se si desidera fare qualcosa se l'input è vuoto, è necessario disporre che il comando non restituisca 0, operazione che può essere eseguita facendo in modo che il caso di successo restituisca uno stato di errore distinguibile:

ifne sh -c 'do_stuff_with_input && exit 255'
case $? in
  0) echo empty;;
  255) echo success;;
  *) echo failure;;
esac

test -t 0non ha nulla a che fare con questo; verifica se l'input standard è un terminale. In un modo o nell'altro non dice nulla sulla disponibilità di input.


Su sistemi con pipe basate su STREAMS (Solaris HP / UX), credo che tu possa usare lo ioctl I_PEEK per sbirciare ciò che è in una pipe senza consumarlo.
Stéphane Chazelas,

@ StéphaneChazelas sfortunatamente non c'è modo di sbirciare i dati da una pipe / fifo su * BSD, quindi nessuna prospettiva per implementare un'utilità portatile peek che potrebbe restituire i dati effettivi da una pipe, non solo quanto ne esiste. (in 4.4 BSD, i tubi 386BSD ecc. sono stati implementati come coppie di prese , ma questo è stato sventrato nelle versioni successive di * BSD - sebbene le mantenessero bidirezionali).
mosvy

bash ha una routine per controllare l'input esposto tramite a read -t 0(t in questo caso significa timeout, se ti chiedi).
Isaac,

11

Una soluzione semplice è usare il ifnecomando (se l'input non è vuoto). In alcune distribuzioni, non è installato per impostazione predefinita. Fa parte del pacchetto moreutilsnella maggior parte delle distribuzioni.

ifne esegue un determinato comando se e solo se l'input standard non è vuoto

Se l'input standard non è vuoto, viene passato ifneal comando dato


2
A partire dal 2017, non è presente per impostazione predefinita in Mac o Ubuntu.
Sridhar Sarnobat,

7

Vecchia domanda, ma nel caso qualcuno lo incontri come ho fatto io: la mia soluzione è leggere con un timeout.

while read -t 5 line; do
    echo "$line"
done

Se stdinè vuoto, questo ritornerà dopo 5 secondi. Altrimenti leggerà tutti gli input e potrai elaborarli secondo necessità.


Mentre mi piace l'idea, -tpurtroppo non fa parte di POSIX: pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html
JepZ

6

controlla se il descrittore di file di stdin (0) è aperto o chiuso:

[ ! -t 0 ] && echo "stdin has data" || echo "stdin is empty"

Quando si passano alcuni dati e si desidera verificare se ce ne sono, si passa comunque all'FD, quindi anche questo non è un buon test.
Jakuje,

1
[ -t 0 ]controlla se fd 0 è aperto a un tty , non se è chiuso o aperto.
mosvy

@mosvy potresti per favore approfondire come ciò influenzerebbe l'utilizzo di quella soluzione in uno script? Ci sono casi in cui non funziona?
JepZ,

@JepZ eh? ./that_script </dev/null=> "stdin ha dati". O ./that_script <&-per avere lo stdin davvero chiuso .
mosvy,

5

È inoltre possibile utilizzare test -s /dev/stdin(in una subshell esplicita).

# test if a pipe is empty or not
echo "string" | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

echo "string" | tail -n+2 | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

: | (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

8
Non funziona per me. Dice sempre che la pipa è vuota.
anfetamachina,

2
Funziona sul mio Mac, ma non sulla mia scatola Linux.
picco

3

In bash:

read -t 0 

Rileva se un input ha dati (senza leggere nulla). Quindi è possibile leggere l'input (se l'input è disponibile al momento dell'esecuzione della lettura):

if     read -t 0
then   read -r input
       echo "got input: $input"
else   echo "No data to read"
fi

Nota: capire che questo dipende dai tempi. Questo rileva se l'input ha già dati solo al momento read -tdell'esecuzione.

Ad esempio, con

{ sleep 0.1; echo "abc"; } | read -t 0; echo "$?"

l'uscita è 1(errore di lettura, ovvero: input vuoto). L'eco scrive alcuni dati ma non è molto veloce da avviare e scrive il suo primo byte, quindi read -t 0segnalerà che il suo input è vuoto, poiché il programma non ha ancora scritto nulla.


github.com/bminor/bash/blob/… - ecco la fonte di come bash rileva che qualcosa è nel descrittore di file.
Pavel Patrin,

Grazie @PavelPatrin
Isaac il

1
@PavelPatrin Non funziona . Come si vede chiaramente dal tuo link, bashfarà uno select()o uno ioctl(FIONREAD)o nessuno dei due, ma non entrambi, come dovrebbe funzionare. read -t0è rotto. Non usarlo
mosvy

Oooh, oggi provo a capire cosa c'è che non va per due ore! Grazie, @mosvy!
Pavel Patrin,

3

Un modo semplice per verificare se ci sono dati disponibili per la lettura in Unix è con FIONREADioctl.

Non riesco a pensare a nessuna utility standard che fa proprio questo, quindi ecco un programma banale che lo fa (meglio di ifnefromoututils IMHO ;-)).

fionread [ prog args ... ]

Se non ci sono dati disponibili su stdin, uscirà con lo stato 1. Se ci sono dati, verrà eseguito prog. Se non progviene fornito, verrà chiuso con lo stato 0.

Puoi rimuovere la pollchiamata se sei interessato solo ai dati immediatamente disponibili. Questo dovrebbe funzionare con la maggior parte dei tipi di fds, non solo con pipe.

fionread.c

#include <unistd.h>
#include <poll.h>
#include <sys/ioctl.h>
#ifdef __sun
#include <sys/filio.h>
#endif
#include <err.h>

int main(int ac, char **av){
        int r; struct pollfd pd = { 0, POLLIN };
        if(poll(&pd, 1, -1) < 0) err(1, "poll");
        if(ioctl(0, FIONREAD, &r)) err(1, "ioctl(FIONREAD)");
        if(!r) return 1;
        if(++av, --ac < 1) return 0;
        execvp(*av, av);
        err(1, "execvp %s", *av);
}

Questo programma funziona davvero? Non è necessario attendere anche un POLLHUPevento per gestire il caso vuoto? Funziona se ci sono più descrizioni dei file sull'altra estremità della pipe?
Gilles 'SO- smetti di essere malvagio' il

Sì funziona. POLLHUP viene restituito solo dal sondaggio, è necessario utilizzare POLLIN per attendere un POLLHUP. Non importa quante maniglie aperte ci siano alle estremità del tubo.
mosvy

Vedi unix.stackexchange.com/search?q=FIONREAD+user%3A22565 per come eseguire FIONREAD da perl (più comunemente disponibile dei compilatori)
Stéphane Chazelas

La routine che utilizza FIONREAD (o HAVE_SELECT) è implementata in bash qui .
Isaac,

2

Se ti piacciono i one-liner corti e criptici:

$ echo "string" | grep . && echo "fill" || echo "empty"
string
fill
$ echo "string" | tail -n+2 | grep . && echo "fill" || echo "empty"
empty

Ho usato gli esempi della domanda originale. Se non si desidera che i dati convogliati utilizzare l' -qopzione con grep


0

Questa sembra essere un'implementazione ifne ragionevole in bash se stai bene leggendo l'intera prima riga

ifne () {
        read line || return 1
        (echo "$line"; cat) | eval "$@"
}


echo hi | ifne xargs echo hi =
cat /dev/null | ifne xargs echo should not echo

5
readrestituirà anche false se l'input non è vuoto ma non contiene caratteri di nuova riga, readesegue alcune elaborazioni sull'input e può leggere più di una riga a meno che non venga chiamato come IFS= read -r line. echonon può essere utilizzato per dati arbitrari.
Stéphane Chazelas,

0

Questo funziona per me usando read -rt 0

esempio dalla domanda originale, senza dati:

echo "string" | tail -n+2 | if read -rt 0 ; then echo has data ; else echo no data ; fi

no, non funziona. provare con { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }(falso negativo) e true | { sleep .1; read -rt0 && echo YES; }(falso positivo). In realtà, bash di readsaranno ingannati anche da FDS aperti in sola scrittura modalità: { read -rt0 && echo YES; cat; } 0>/tmp/foo. L'unica cosa che sembra fare è una select(2)cosa del genere.
mosvy

... e selectrestituirà un fd come "pronto" se un read(2)on non si bloccherà, non importa se ritornerà EOFo un errore. Conclusione: read -t0è rotto in bash. Non usarlo.
mosvy

@mosvy L'hai segnalato con bashbug?
Isaac,

@mosvy Il { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }non è un falso negativo perché (al momento della lettura) non c'è input. Successivamente (sleep .1) quell'input è disponibile (per il gatto).
Isaac,

@mosvy Perché l' opzione r influisce sul rilevamento ?: echo "" | { read -t0 && echo YES; }stampa SÌ ma echo "" | { read -rt0 && echo YES; }non lo fa.
Isaac,
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.