web-dev-qa-db-fra.com

Comment vérifier si un tube est vide et exécuter une commande sur les données s'il ne l'est pas?

J'ai canalisé une ligne dans le script bash et je veux vérifier si le tube contient des données, avant de les alimenter dans un programme.

Recherche que j'ai trouvée sur test -t 0 mais ça ne marche pas ici. Retourne toujours faux. Alors, comment être sûr que le tuyau contient des données?

Exemple:

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

Sortie: fill

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

Sortie: fill

Contrairement à manière standard/canonique pour tester si le pipeline précédent a produit une sortie? l'entrée doit être préservée pour la transmettre au programme. Cela généralise Comment diriger la sortie d'un processus vers un autre mais ne l'exécuter que si le premier a une sortie? qui se concentre sur l'envoi d'e-mails.

49
zetah

Il n'y a aucun moyen de jeter un œil au contenu d'un canal à l'aide des utilitaires Shell couramment disponibles, ni de lire un caractère du canal puis de le remettre. La seule façon de savoir qu'un canal contient des données est de lire un octet, puis vous devez obtenir cet octet à sa destination.

Faites donc cela: lisez un octet; si vous détectez une fin de fichier, faites ce que vous voulez faire lorsque l'entrée est vide; si vous lisez un octet, bifurquez ce que vous voulez faire lorsque l'entrée n'est pas vide, canalisez cet octet dedans et canalisez le reste des données.

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'utilitaire ifne de Moreutils de Joey Hess exécute une commande si son entrée n'est pas vide. Il n'est généralement pas installé par défaut, mais il devrait être disponible ou facile à construire sur la plupart des variantes Unix. Si l'entrée est vide, ifne ne fait rien et renvoie l'état 0, qui ne peut pas être distingué de la commande exécutée avec succès. Si vous voulez faire quelque chose si l'entrée est vide, vous devez prendre des dispositions pour que la commande ne retourne pas 0, ce qui peut être fait en demandant au cas de réussite de renvoyer un état d'erreur distinct:

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

test -t 0 n'a rien à voir avec cela; il teste si l'entrée standard est un terminal. Il ne dit rien dans un sens ou dans l'autre quant à la disponibilité d'une entrée.

Une solution simple consiste à utiliser la commande ifne (si l'entrée n'est pas vide). Dans certaines distributions, il n'est pas installé par défaut. Il fait partie du package moreutils dans la plupart des distributions.

ifne exécute une commande donnée si et seulement si l'entrée standard n'est pas vide

Notez que si l'entrée standard n'est pas vide, elle est passée par ifne à la commande donnée

14
Nick Wirth

Vieille question, mais au cas où quelqu'un tomberait dessus comme moi: ma solution est de lire avec un timeout.

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

Si stdin est vide, cela reviendra après 5 secondes. Sinon, il lira toutes les entrées et vous pourrez les traiter au besoin.

9
Ladd

vérifier si le descripteur de fichier de stdin (0) est ouvert ou fermé:

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

Si vous aimez les monolignes courtes et cryptiques:

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

J'ai utilisé les exemples de la question d'origine. Si vous ne voulez pas que les données redirigées utilisent -q option avec grep

5
yashma

Un moyen simple de vérifier s'il y a des données disponibles pour la lecture sous Unix est avec l'ioctl FIONREAD.

Je ne peux pas penser à un utilitaire standard faisant exactement cela, alors voici un programme trivial qui le fait (mieux que le ifne de moreutils IMHO ;-)).

fionread [ prog args ... ]

S'il n'y a pas de données disponibles sur stdin, il quittera avec le statut 1. S'il y a des données, il exécutera prog. Si aucun prog n'est donné, il quittera avec le statut 0.

Cela devrait fonctionner avec la plupart des types de descripteurs de fichiers, et pas seulement avec les canaux.

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);
}
4
mosvy

Vous pouvez utiliser test -s /dev/stdin (dans un sous-shell explicite) également.

# 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')
4
trent55

En bash:

read -t 0 

Détecte si une entrée contient des données (sans rien lire). Ensuite, vous pouvez lire l'entrée (si l'entrée est disponible au moment où la lecture est exécutée):

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

Remarque: Comprenez que cela dépend du timing. Cela détecte si l'entrée contient déjà des données uniquement au moment read -t s'exécute.

Par exemple, avec

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

la sortie est 1 (échec de lecture, c'est-à-dire: entrée vide). L'écho écrit certaines données mais n'est pas très rapide pour démarrer et écrire son premier octet, donc, read -t 0 signalera que son entrée est vide, car le programme n'a encore rien écrit.

2
Isaac

Cela semble être une implémentation ifne raisonnable dans bash si vous êtes d'accord avec la lecture de la première ligne

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


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

Cela fonctionne pour moi en utilisant read -rt 0

exemple de question originale, sans données:

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

Un autre hack que vous pouvez essayer utilise timeout pour lire à partir de /dev/stdin comme ça:

timeout 1s cat /dev/stdin > /tmp/input
if [ $? -eq 124 ] && ! [ -s /tmp/input ]
then
  echo "No input provided."
else
  echo "Input provided"
  # Optionally, you can move or read from /tmp/input...whatever your program needs to do.
fi
0
Carlos Nunez