Si j'appelle une commande, par exemple, echo
je peux utiliser les résultats de cette commande dans plusieurs autres commandes avec tee
. Exemple:
echo "Hello world!" | tee >(command1) >(command2) >(command3)
Avec cat, je peux collecter les résultats de plusieurs commandes. Exemple:
cat <(command1) <(command2) <(command3)
Je voudrais pouvoir faire les deux choses en même temps, afin que je puisse utiliser tee
pour appeler ces commandes sur la sortie de quelque chose d'autre (par exemple, echo
j'ai écrit), puis collecter tous leurs résultats sur une seule sortie avec cat
.
Il est important de garder les résultats dans l'ordre, cela signifie que les lignes dans la sortie de command1
, command2
et command3
ne doivent pas être entrelacées, mais ordonnées comme les commandes sont (comme cela arrive avec cat
).
Il peut y avoir de meilleures options que cat
et , tee
mais ce sont ceux que je connais jusqu'à présent.
Je veux éviter d'utiliser des fichiers temporaires car la taille de l'entrée et de la sortie peut être importante.
Comment pourrais-je faire ça?
PD: un autre problème est que cela se produit en boucle, ce qui rend la gestion des fichiers temporaires plus difficile. C'est le code actuel que j'ai et il fonctionne pour les petits tests, mais il crée des boucles infinies lors de la lecture et de l'écriture à partir du fichier auxiliaire d'une manière que je ne comprends pas.
somefunction()
{
if [ $1 -eq 1 ]
then
echo "Hello world!"
else
somefunction $(( $1 - 1 )) > auxfile
cat <(command1 < auxfile) \
<(command2 < auxfile) \
<(command3 < auxfile)
fi
}
Les lectures et les écrits dans auxfile semblent se chevaucher, ce qui fait tout exploser.
la source
echo HelloWorld > file; (command1<file;command2<file;command3<file)
soit pour la sortieecho | tee cmd1 cmd2 cmd3; cat cmd1-output cmd2-output cmd3-output
. C'est comme ça que ça fonctionne - tee ne peut fourcher des entrées que si toutes les commandes fonctionnent et sont traitées en parallèle. si une commande est en sommeil (parce que vous ne voulez pas d'entrelacement), elle bloquera simplement toutes les commandes, afin d'éviter de remplir la mémoire en entrée ...Réponses:
Vous pouvez utiliser une combinaison de GNU stdbuf et
pee
de moreutils :pipi
popen(3)
s ces 3 lignes de commande shell, puisfread
s l'entrée etfwrite
s tous les trois, qui seront mis en mémoire tampon jusqu'à 1M.L'idée est d'avoir un tampon au moins aussi grand que l'entrée. De cette façon, même si les trois commandes sont démarrées en même temps, elles ne verront l'entrée entrer que lorsque
pee
pclose
les trois commandes seront séquentiellement.À chaque fois
pclose
,pee
vide le tampon de la commande et attend sa fin. Cela garantit que tant que cescmdx
commandes ne commenceront rien à produire avant d'avoir reçu une entrée (et ne déclenchent pas un processus qui peut continuer à sortir après le retour de leur parent), la sortie des trois commandes ne sera pas entrelacé.En effet, c'est un peu comme utiliser un fichier temporaire en mémoire, avec l'inconvénient que les 3 commandes sont démarrées simultanément.
Pour éviter de démarrer les commandes simultanément, vous pouvez écrire
pee
comme une fonction shell:Mais attention, les shells autres que ceux
zsh
qui échoueraient pour une entrée binaire avec des caractères NUL.Cela évite d'utiliser des fichiers temporaires, mais cela signifie que toute l'entrée est stockée en mémoire.
Dans tous les cas, vous devrez stocker l'entrée quelque part, en mémoire ou dans un fichier temporaire.
En fait, c'est une question assez intéressante, car elle nous montre la limite de l'idée Unix d'avoir plusieurs outils simples coopérant à une seule tâche.
Ici, nous aimerions que plusieurs outils coopèrent à la tâche:
echo
)tee
)cmd1
,cmd2
,cmd3
)cat
).Ce serait bien s'ils pouvaient tous fonctionner ensemble en même temps et faire leur travail acharné sur les données qu'ils sont censés traiter dès qu'elles sont disponibles.
Dans le cas d'une commande de filtre, c'est simple:
Toutes les commandes sont exécutées simultanément,
cmd1
commence à grignoter des donnéessrc
dès qu'elles sont disponibles.Maintenant, avec trois commandes de filtrage, nous pouvons toujours faire la même chose: démarrez-les simultanément et connectez-les avec des tuyaux:
Ce que nous pouvons faire relativement facilement avec des pipes nommées :
(Au-dessus de, il
} 3<&0
s'agit de contourner le fait que les&
redirectionsstdin
depuis/dev/null
, et nous utilisons<>
pour éviter l'ouverture des tuyaux à bloquer jusqu'à ce que l'autre extrémité (cat
) soit également ouverte)Ou pour éviter les pipes nommées, un peu plus douloureusement avec
zsh
coproc:Maintenant, la question est: une fois tous les programmes démarrés et connectés, les données circuleront-elles?
Nous avons deux contraintes:
tee
alimente toutes ses sorties au même taux, il ne peut donc envoyer des données qu'au taux de son canal de sortie le plus lent.cat
ne commencera la lecture à partir du deuxième tuyau (tuyau 6 dans le dessin ci-dessus) que lorsque toutes les données auront été lues à partir du premier (5).Cela signifie que les données ne circuleront pas dans le tuyau 6 avant la
cmd1
fin. Et, comme dans le castr b B
ci - dessus, cela peut signifier que les données ne circuleront pas non plus dans le tuyau 3, ce qui signifie qu'elles ne circuleront dans aucun des tuyaux 2, 3 ou 4, car ellestee
se nourrissent au débit le plus lent des 3.En pratique, ces canaux ont une taille non nulle, donc certaines données réussiront à passer, et sur mon système au moins, je peux le faire fonctionner jusqu'à:
Au-delà, avec
Nous avons une impasse, où nous sommes dans cette situation:
Nous avons rempli les tuyaux 3 et 6 (64 ko chacun).
tee
a lu cet octet supplémentaire, il l'a alimentécmd1
, maiscmd2
de le vidercmd2
ne peut pas le vider car il est bloqué en train d'écrire sur le pipe 6, en attendantcat
de le vidercat
ne peut pas le vider car il attend qu'il n'y ait plus d'entrée sur le tuyau 5.cmd1
ne peut pas direcat
qu'il n'y a plus d'entrée car il attend lui-même plus d'entréetee
.tee
ne peut pas direcmd1
qu'il n'y a plus d'entrée car elle est bloquée ... et ainsi de suite.Nous avons une boucle de dépendance et donc un blocage.
Maintenant, quelle est la solution? De plus gros tuyaux 3 et 4 (assez gros pour contenir toute
src
la sortie de) le feraient. Nous pourrions le faire par exemple en insérantpv -qB 1G
entretee
etcmd2/3
oùpv
pourrait stocker jusqu'à 1G de données en attentecmd2
etcmd3
en lecture. Cela signifierait cependant deux choses:cmd2
ne commencerait en réalité à traiter les données que lorsque cmd1 serait terminé.Une solution au deuxième problème consisterait à agrandir également les tuyaux 6 et 7. En supposant cela
cmd2
et encmd3
produisant autant de sortie qu’ils consomment, cela ne consommerait pas plus de mémoire.La seule façon d'éviter la duplication des données (dans le premier problème) serait d'implémenter la rétention des données dans le répartiteur lui-même, c'est-à-dire de mettre en œuvre une variante
tee
qui peut alimenter les données au rythme de la sortie la plus rapide (conserver les données pour alimenter le les plus lents à leur rythme). Pas vraiment banal.Donc, au final, le meilleur que nous pouvons raisonnablement obtenir sans programmation est probablement quelque chose comme (syntaxe Zsh):
la source
+1
pour le bel art ASCII :-)Ce que vous proposez ne peut pas être fait facilement avec une commande existante et n'a pas beaucoup de sens de toute façon. L'idée générale des canaux (
|
sous Unix / Linux) est que danscmd1 | cmd2
lacmd1
sortie d'écriture (au plus) jusqu'à ce qu'un tampon de mémoire se remplisse, puiscmd2
exécute la lecture des données du tampon (au plus) jusqu'à ce qu'il soit vide. C'est-à-dire,cmd1
etcmd2
fonctionner en même temps, il n'est jamais nécessaire d'avoir plus qu'une quantité limitée de données "en vol" entre eux. Si vous souhaitez connecter plusieurs entrées à une seule sortie, si l'un des lecteurs est à la traîne des autres, soit vous arrêtez les autres (quel est l'intérêt de fonctionner en parallèle alors?) Soit vous cachez la sortie que le retardataire n'a pas encore lue (à quoi bon alors ne pas avoir de fichier intermédiaire?). plus complexe.Dans mes presque 30 ans d'expérience sur Unix, je ne me souviens d'aucune situation qui aurait vraiment profité à un tel tube à sorties multiples.
Vous pouvez combiner plusieurs sorties en un seul aujourd'hui flux, tout en aucune façon entrelacée (comment les sorties
cmd1
etcmd2
entrelacer? Une ligne à son tour? Tour de rôle d' écriture de 10 octets? Alternate « paragraphes » définis en quelque sorte? Et si l' on vient n » t écrire quoi que ce soit pendant longtemps - tout cela est complexe à gérer). Il est effectué par exemple par(cmd1; cmd2; cmd3) | cmd4
les programmescmd1
,cmd2
etcmd3
est exécuté l'un après l'autre, la sortie est envoyée en entrée àcmd4
.la source
Pour votre problème de chevauchement, sur Linux (et avec
bash
ouzsh
mais pas avecksh93
), vous pouvez le faire comme:Notez l'utilisation de
(...)
au lieu de{...}
pour obtenir un nouveau processus à chaque itération afin que nous puissions avoir un nouveau fd 3 pointant vers un nouveauauxfile
.< /dev/fd/3
est une astuce pour accéder à ce fichier maintenant supprimé. Il ne fonctionnera pas sur des systèmes autres que Linux où< /dev/fd/3
est similairedup2(3, 0)
et donc fd 0 serait ouvert en mode écriture seule avec le curseur à la fin du fichier.Pour éviter le fork de la fonction imbriquée, vous pouvez l'écrire comme suit:
Le shell se chargerait de sauvegarder le fd 3 à chaque itération. Vous finiriez par manquer de descripteurs de fichiers plus tôt.
Bien que vous constatiez qu'il est plus efficace de le faire comme:
Autrement dit, ne pas imbriquer les redirections.
la source