2015-10-21 1 views
3

Est-il possible de diviser STDIN entre plusieurs lecteurs, devenant ainsi une file d'attente? Je voudrais passer chaque ligne à un seul lecteur. Les canaux nommés presque travail, mais en même temps se lit interfèrent:Plusieurs lecteurs de FIFO

reader.sh

#!/usr/bin/env bash 
while read line 
do 
    echo $line 
done < fifo 

writer.sh

#!/usr/bin/env bash 
while true 
do 
    echo "This is a test sentance" 
    sleep 1 
done 

exécution:

mkfifo fifo 
./reader.sh & 
./reader.sh & 
./writer.sh > fifo 

sortie occasionnelle (surtout si les lecteurs et les écrivains sont séparés w indows)

This is atetsnac 
Ti sats etnesats etne etsnac 
isats etnes etsnac 
Tisi etsnac 
hi etsnac 
Ti sats etn 
hsi etsnac 

Notes:

  • Je sais qu'il ya de meilleures approches, juste curieux de savoir si cela pourrait être fait pour travailler
  • Je suppose que ce n'est pas un bug comme je l'ai testé à la fois Linux et OSX boîtes
  • Je voudrais un consommateur par ligne, ce qui exclut les tee
  • Je voudrais consommer STDIN, qui exclut xargs
  • G NU coreutils split peut allouer round robin, mais pas d'abord disponible
  • GNU parallèle --pipe attend jusqu'à la fermeture de STDIN; Je voudrais allouer dès que possible
+0

Je reproduit ici ce (OS X), mais je ne comprends pas. – Barmar

+0

Je pense que je comprends. Alors que les écritures sur un tube sont atomiques (à condition qu'elles soient plus petites que BUFSIZ), les limites des messages ne sont pas enregistrées dans le tube. Ainsi, les lecteurs concurrents peuvent chacun lire différentes parties d'une entrée. L'appel système pour la lecture d'un flux ne fournit aucun moyen de demander une ligne entière en tant qu'unité. – Barmar

+0

Voir http://stackoverflow.com/questions/20597149/multiple-read-processes-of-the-same-pipe-can-all-read-the-same-message – Barmar

Répondre

-1

Vous pouvez changer le lecteur à être

#!/usr/bin/env bash 
cat fifo | while read line 
do 
    echo $line 
done 

De cette façon, il va lire une ligne entière ou rien. Le problème avec l'autre version est que la responsabilité de la lecture du fifo était pour le read intégré, qui utilise un tampon de 1 caractère pour la lecture, de sorte que les caractères différents de la même ligne peuvent être lus par deux processus s'ils sont en cours d'exécution simultanément. Vous pouvez le voir avec strace:

strace bash -c 'while read line; do echo $line; done < fifo'` 

cat utilise un tampon plus grand pour lire, il finit par recevoir une ligne entière. Testez-le avec:

strace cat fifo | while read line; do echo $line; done 

Cependant, je ne recommande pas l'utiliser comme une file d'attente d'emploi, car il ne semble pas distribuer uniformément sur lit les lecteurs.

+1

De plus, cat lira tout dans STDIN, donc videra toute une file d'attente multiligne si elle est présente. Toutes les commandes de ligne unique (tête, sed, awk) sortent après la lecture, fermant le tuyau. Y at-il une commande pour lire continuellement une ligne entière à la fois (si seulement la tête avait une option -f -follow comme la queue)? – user3769065

+0

Il est impossible de lire _just_ une seule ligne s'il y en a plus dans le tampon, car l'opération 'read()' brute fonctionne en lisant un nombre donné d'octets, et non en lisant _until_ un caractère quelconque. Les programmes qui lisent seulement une ligne le font en lisant un caractère à la fois (comme je l'ai expliqué dans la réponse pour le 'read' intégré), ainsi ils s'entrelaceront avec d'autres, causant le problème que vous essayiez de résoudre. –

1

Non, il n'est pas possible de le faire de manière robuste en général. Écrit dans un tube nommé inférieur à PIPE_BUF (> = 512 octets sur tous les systèmes POSIX) sont atomiques. Le problème est que les lectures ne sont pas atomiques, et il n'y a pas de façon standard (ou non standard AFAIK) de les faire. Sur une lecture bloquante du tube si un ou plusieurs octets sont disponibles, ils seront lus immédiatement avec la lecture du nombre réel renvoyé comme valeur de retour. "Comme il n'y a aucune garantie d'atomicité, vous ne devez jamais autoriser plusieurs lecteurs à moins que vous ayez un autre mécanisme de contrôle de concurrence en place ... utilisez plutôt quelque chose comme une file d'attente de messages." - Advance UNIX Programming.Cela dit, pour s'amuser, il est possible d'obtenir un comportement étonnamment robuste. La raison pour laquelle la ligne cat | while read line do; .. approche semble fonctionner est parce que cat arrache immédiatement les lignes du tuyau dès qu'ils arrivent, et les lecteurs sont prêts à lire dès que l'écriture commence, comme vous l'avez mentionné. Parce qu'il lit tout de suite il arrive d'arracher des lignes (pluriel) aux limites de la ligne à laquelle ils sont écrits. En général, une approche par ligne ne va pas être très robuste, car la limite du message n'est pas prévisible.

Si vous avez écrit et lu dans des blocs de taille constante < = PIPE_BUF vous feriez mieux. Votre garantie de ne jamais lire plus que ce que vous demandez, et aussi longtemps que vous écrivez des morceaux de taille constante, moins de taille PIPE_BUF avec chaque écriture il n'y a aucune raison qu'il devrait jamais y avoir moins d'un multiple d'un bloc d'octets disponibles pour lire - sauf pour le fait que ce n'est pas garanti et donc se produira probablement pour une raison quelconque.

reader.sh:

#!/bin/bash 
while read -N 21 packet 
do 
    echo [$$] $packet 
done<fifo 

writer.sh

#!/bin/bash 
for((i=0; i<100; i++)) 
do 
    s=`printf "%020d" $i` 
    echo $s 
    echo "wrote $s" >&2 
done 

exécution:

mkfifo fifo 
./reader.sh & 
./reader.sh & 
./writer.sh > fifo