Tamponner complètement la sortie de la commande avant de diriger vers une autre commande?


10

Existe-t-il un moyen d'exécuter une commande uniquement après qu'une autre soit effectuée sans fichier temporaire? J'ai une commande plus longue et une autre commande qui formate la sortie et l'envoie à un serveur HTTP en utilisant curl. Si je viens d'exécuter commandA | commandB, commandBdémarre curl, se connecte au serveur et commence à envoyer des données. Parce que cela commandAprend tellement de temps, le serveur HTTP expirera. Je peux faire ce que je veux aveccommandA > /tmp/file && commandB </tmp/file && rm -f /tmp/file

Par curiosité, je veux savoir s'il existe un moyen de le faire sans le fichier temporaire. J'ai essayé mbuffer -m 20M -q -P 100mais le processus de curl est toujours démarré dès le début. Mbuffer attend jusqu'à ce que commandAla transmission des données soit terminée. (Les données elles-mêmes ne font que quelques centaines de ko au maximum)


Et alors commandA && commandB?
eyoung100

1
qui ne transmet pas la sortie de commandAà commandB, n'est-ce pas?
Josef dit Réintégrer Monica

Il démarre la commande B si la commande A se termine avec succès, ce qui signifie que la boucle ne démarre pas tôt.
eyoung100

2
@ eyoung100 mais il ne transmet pas stdout de commandA à stdin pour commandB, ce dont Josef a besoin!
roaima

S'il veut que la sortie soit passée, il doit utiliser un fichier. Voir la réponse de cuonglm.
eyoung100

Réponses:


14

Ceci est similaire à quelques autres réponses. Si vous avez le paquet «moreutils», vous devriez avoir la spongecommande. Essayer

commandA | sponge | { IFS= read -r x; { printf "%s\n" "$x"; cat; } | commandB; }

La spongecommande est fondamentalement un filtre pass-through (comme cat) sauf qu'elle ne commence à écrire la sortie qu'après avoir lu l'intégralité de l'entrée. C'est-à-dire qu'il «absorbe» les données, puis les libère lorsque vous les serrez (comme une éponge). Donc, dans une certaine mesure, c'est de la «tricherie» - s'il y a une quantité non négligeable de données, spongeutilise presque certainement un fichier temporaire. Mais c'est invisible pour vous; vous n'avez pas à vous soucier des tâches ménagères comme le choix d'un nom de fichier unique et le nettoyage par la suite.

Le { IFS= read -r x; { printf "%s\n" "$x"; cat; } | commandB; } lit la première ligne de sortie de sponge. N'oubliez pas que cela n'apparaît que lorsque vous commandAavez terminé.  Ensuite, il se déclenche commandB, écrit la première ligne dans le tuyau et appelle catpour lire le reste de la sortie et l'écrire dans le tuyau.


Merci! Il en spongeva de même pour cette chose que j'ai utilisée mbuffer, mais qui semble mieux adaptée ici. utiliser la lecture est intelligent ici. Je m'en souviendrai certainement pour l'avenir.
Josef dit Réintégrer Monica

@Josef: Je n'en ai jamais entendu parler mbufferauparavant; cela pourrait en fait être aussi bon que sponge. Je suis d'accord que l'utilisation readest une astuce astucieuse. Je ne peux pas m'en attribuer le mérite; il apparaît de temps en temps dans les réponses de Stack Exchange (U&L, Super User, Ask Ubuntu, etc.). En fait, la réponse de roaima à cette question est très similaire à la mienne, sauf qu'elle n'utilise pas sponge(ou quelque chose d'équivalent), donc, comme je l'ai mentionné dans un commentaire, cela ne retarde pas le démarrage commandBautant que vous en avez besoin (dans ma compréhension de ton problème).
G-Man dit `` Réintègre Monica ''

github.com/ildar-shaimordanov/perl-utils#sponge a une version script de "sponge" enveloppée dans une fonction bash.
marinara

5

Les commandes dans le pipeline sont démarrées simultanément, vous devez stocker la commandAsortie quelque part pour l'utiliser plus tard. Vous pouvez éviter le fichier temporaire en utilisant la variable:

output=$(command A; echo A)
printf '%s' "${output%%A}" | commandB

Quel est le but de la echo Asubstitution dans le processus?
Digital Trauma

3
@DigitalTrauma: il empêche la substitution de commandes de supprimer les sauts de ligne de fin.
cuonglm

Ah, je vois, oui - bonne capture d'un éventuel étui d'angle
Digital Trauma

J'ai réalisé que ma réponse était incorrecte, alors je l'ai supprimée. Je pense que cela semble correct à la place. +1
Digital Trauma

Merci beaucoup! C'est génial et surtout l'écho A / %% A pour garder la nouvelle ligne (même si je ne l'exige pas)
Josef dit Réintégrer Monica

1

Je ne connais aucun utilitaire UNIX standard pouvant résoudre ce problème. Une option serait d'utiliser awkpour accumuler la commandAsortie et la rincer commandBà un coup, comme si

commandA  | awk '{x = x ORS $0}; END{printf "%s", x | "commandB"}'

Attention, cela pourrait être gourmand en mémoire car il awks'agit de construire une chaîne à partir de son entrée.


1
Cela supprime la dernière nouvelle ligne. En supposant que le dernier caractère est une nouvelle ligne, vous avez besoin d'un \nou d'un autre ORSà la fin.
G-Man dit «Réintègre Monica»

0

Vous pouvez résoudre l'exigence avec un petit script. Cette variante particulière évite le fichier temporaire et le porc de mémoire potentiel au détriment de processus supplémentaires.

#!/bin/bash
#
IFS= read LINE

if test -n "$LINE"
then
    test -t 2 && echo "Starting $*" >&2
    (
        echo "$LINE"
        cat

    ) | "$@"
else
    exit 0
fi

Si vous deviez appeler le script waituntil(et le rendre exécutable, le mettre dans le PATH, etc.), vous l'utiliseriez comme ceci

commandA... | waituntil commandB...

Exemple

( sleep 3 ; date ; id ) | waituntil nl

1
Tout cela ne fait que retarder le démarrage commandBjusqu'à ce qu'il commandAait écrit sa première ligne de sortie . Ce n'est probablement pas assez bon.
G-Man dit `` Réintègre Monica ''

@ G-Man mais nous ne savons pas. Je vous aime sponge. Off pour lire sur celui-ci maintenant.
roaima
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.