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.
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
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.
vérifier si le descripteur de fichier de stdin (0) est ouvert ou fermé:
[ ! -t 0 ] && echo "stdin has data" || echo "stdin is empty"
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
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);
}
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')
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.
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
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
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