Python: sous-processus.appel, stdout vers un fichier, stderr dans un fichier, afficher stderr sur l'écran en temps réel
J'ai un outil de ligne de commande (en fait plusieurs) que je suis en train d'écrire un wrapper pour en Python.
L'outil est généralement utilisé comme ceci:
$ path_to_tool -option1 -option2 > file_out
L'utilisateur obtient la sortie écrit à file_out, et est également en mesure de voir les différents messages d'état de l'outil, comme il est en cours d'exécution.
Je veux reproduire ce comportement, tout en également la journalisation des stderr (les messages d'état) à un fichier.
Ce que j'ai est: est-ce
from subprocess import call
call(['path_to_tool','-option1','option2'], stdout = file_out, stderr = log_file)
Cela fonctionne bien SAUF que stderr n'est pas écrit à l'écran.
Je peux ajouter du code pour imprimer le contenu de la log_file à l'écran bien sûr, mais ensuite l'utilisateur va le voir après tout est fait, plutôt que de tout ce qui se passe.
Pour résumer, comportement désiré est:
- d'appel(), ou sous-processus()
- direct stdout vers un fichier
- direct stderr dans un fichier, tout aussi écrit stderr à l'écran en temps réel, comme si le
l'outil a été appelée directement à partir de la ligne de commande.
J'ai le sentiment que je suis absent quelque chose de vraiment simple, ou c'est beaucoup plus compliqué que ce que je pensais...merci pour toute aide!
EDIT: cela ne doit fonctionner sur Linux.
- Est-ce que votre code fonctionne sur Windows (ou d'autres non-POSIXy plates-formes)? Si non, il est plus facile de répondre.
- Il n'a pas besoin d'!
- connexes: Python sous-processus les enfants à la sortie vers un fichier et le terminal?
- liés à "temps réel" de la partie: Python: lire le streaming d'entrée de sous-processus.communiquer()
Vous devez vous connecter pour publier un commentaire.
Vous peut le faire avec
subprocess
, mais il n'est pas trivial. Si vous regardez la Fréquemment Utilisé Des Arguments dans les docs, vous verrez que vous pouvez passerPIPE
comme lestderr
argument, qui crée une nouvelle pipe, passe d'un côté du tuyau pour le processus de l'enfant, et fait de l'autre côté à la disposition de l'utiliser commestderr
attribut.*Ainsi, vous aurez besoin pour l'entretien de la canalisation, de l'écriture à l'écran et dans le fichier. En général, les détails de droite pour cela est très difficile.** Dans votre cas, il n'y a qu'un seul canal, et vous avez l'intention de l'entretenir de manière synchrone, c'est pas si mal que ça.
(À noter qu'il y a quelques problèmes à l'aide
for line in proc.stderr:
—en fait, si ce que vous êtes en train de lire s'avère ne pas être ligne-mis en mémoire tampon pour une raison quelconque, vous pouvez vous asseoir autour d'attente pour un retour à la ligne, même si il y a effectivement une demi-ligne de données à traiter. Vous pouvez lire les morceaux à la fois, avec, disons,read(128)
, ou mêmeread(1)
, pour obtenir les données de manière plus fluide, si nécessaire. Si vous avez besoin d'obtenir effectivement chaque octet dès qu'il arrive, et ne peuvent pas se permettre le coût deread(1)
, vous aurez besoin de mettre le tuyau en mode sans blocage et de lire de manière asynchrone.)Mais si vous êtes sous Unix, il pourrait être plus simple d'utiliser la
tee
de commande pour le faire pour vous.Pour un quick&solution sale, vous pouvez utiliser le shell pour tuyau à travers elle. Quelque chose comme ceci:
Mais je ne veux pas de shell de débogage de la tuyauterie; nous allons le faire en Python, comme le montre dans les docs:
Enfin, il y a une douzaine ou plus de niveau supérieur, les wrappers autour de sous-processus et/ou de l'environnement sur PyPI—
sh
,shell
,shell_command
,shellout
,iterpipes
,sarge
,cmd_utils
,commandwrapper
, etc. Recherche pour "shell", "sous-processus", "processus", "ligne de commande", etc. et de trouver celui que vous aimez qui rend le problème trivial.Que faire si vous avez besoin de recueillir à la fois stderr et stdout?
Le moyen facile de le faire est de simplement rediriger l'un à l'autre, comme Sven Marnach suggère dans un commentaire. Il suffit de changer le
Popen
paramètres comme ceci:Et puis partout vous avez utilisé
tool.stderr
, utiliseztool.stdout
au lieu—par exemple, pour le dernier exemple:Mais cela a un compromis. De toute évidence, le mélange des deux ruisseaux ensemble signifie que vous ne pouvez pas vous connecter stdout pour file_out et stderr de log_file, ou copie stdout pour votre stdout et stderr de votre stderr. Mais cela signifie également que la commande peut être non-déterministe—si le sous-processus toujours écrit deux lignes sur la sortie stderr avant d'écrire quelque chose sur la sortie standard, vous pourriez finir par avoir un tas de stdout entre ces deux lignes une fois que vous mélangez les cours d'eau. Et cela signifie qu'ils doivent partager les la sortie standard du mode de mise en mémoire tampon, de sorte que si vous étiez en s'appuyant sur le fait que linux/glibc garanties stderr être tamponnée (sauf si le sous-processus explicitement change), qui peut ne plus être vrai.
Si vous avez besoin pour gérer les deux processus séparément, il devient de plus en plus difficile. Plus tôt, j'ai dit que l'entretien de la conduite à la volée est facile aussi longtemps que vous avez un tuyau et peut-service de manière synchrone. Si vous avez deux tuyaux, qui de toute évidence n'est plus vrai. Imaginez que vous êtes en attente sur
tool.stdout.read()
, et de nouvelles données à partir detool.stderr
. Si il y a trop de données, il peut causer le tuyau de débordement et le processus secondaire à bloc. Mais même si cela n'arrive pas, de toute évidence vous ne serez pas en mesure de lire les journaux et le stderr de données jusqu'à ce que quelque chose vient de stdout.Si vous utilisez le canal de communication par le biais du
tee
solution, qui évite le problème initial... mais seulement par la création d'un nouveau projet qui est tout aussi mauvais. Vous avez deuxtee
cas, et pendant que vous êtes à l'appel decommunicate
sur l'un, l'autre est assis autour d'attente pour toujours.Donc, de toute façon, vous avez besoin d'une sorte de mécanisme asynchrone. Vous pouvez le faire avec les fils, un
select
réacteur, quelque chose commegevent
, etc.Voici un moyen rapide et sale exemple:
Toutefois, il existe certains cas où cela ne marchera pas. (Le problème, c'est l'ordre dans lequel SIGCHLD et SIGPIPE/EPIPE/expressions du FOLKLORE arriver. Je ne pense pas que tout cela va nous affecter, car nous ne sommes pas d'envoi de l'entrée... mais ne me faites pas confiance sur qui à tort et à travers et/ou de test.) Le
sous-processus.communiquer
fonction de 3,3+ obtient toutes les délicats détails à droite. Mais vous pouvez trouver beaucoup plus simple d'utiliser l'un de l'async-sous-processus wrapper implémentations vous pouvez trouver sur PyPI et ActiveState, ou même les choses à partir d'un sous-processus à part entière async cadre comme Tordu.* Les documents ne sont pas vraiment expliquer ce que les tuyaux sont, presque comme s'ils attendent que vous pour être un vieux C Unix main... Mais quelques-uns des exemples, en particulier dans le Remplacement des Anciens Fonctions avec le
sous-processus
Module l'article, de montrer comment ils sont utilisés, et c'est assez simple.** La partie la plus difficile est de séquençage de deux ou plusieurs tuyaux correctement. Si vous attendez sur un tuyau, l'autre peut déborder et de bloquer, empêcher votre attente sur l'autre de la finition. Le moyen le plus facile de contourner ce problème est de créer un thread de service de chaque tuyau. (Sur la plupart des *nix plates-formes, vous pouvez utiliser un
select
oupoll
réacteur au lieu de cela, mais de la croix-plate-forme est incroyablement difficile.) La source pour le module, en particuliercommunicate
et ses assistants, montre comment le faire. (Je l'ai lié à 3.3, parce que dans les versions antérieures,communicate
lui-même obtient des choses importantes que de mal...) C'est pourquoi, chaque fois que possible, vous souhaitez utilisercommunicate
si vous avez besoin de plus d'une pipe. Dans votre cas, vous ne pouvez pas utilisercommunicate
, mais heureusement, vous n'avez pas besoin de plus d'une pipe.tool
ettee
. Suivant l'exemple de code d'un peu trop près. 🙂 Merci pour l'attraper.2|
censé pipe stderr? Il n'est pas dans le shell POSIX.shell=True
code sans y penser, en espérant (mais à ne pas vérifier), il y aurait plusieurs erreurs, comme d'un segue à montrer comment le faire lisiblement en Python. D'où la "je ne veux pas le shell de débogage de la tuyauterie" de ligne.sys.stderr
. Ce que nous voyons dansproc.stderr
est une pipe. Et ce que le processus secondaire n'est à son stderr est entièrement à la sous-processus; nous n'avons aucun contrôle sur elle. Lequel de ces vous demander à propos de?bufsize
paramètrePopen()
, mais bien sûr, cela n'affecte pas le processus secondaire' stderr.tee
, généralement, vous perdez une sortie en temps réel. Ne vous arrive de savoir si la même chose est vraie pour la sortie stderr?isatty
explicitement et de se comporter différemment, trop. Je te suggère de tester la réelle outils sur quelle plate-forme(s) qui vous intéressent. Si le pire arrive au pire, vous devrez peut-être créer unpty
, qui est toujours un plaisir de faire une croix-plate-forme (je parle juste de la croix-Unix; évidemment, il n'est même pas significative sur Windows...) de manière à en Python.man stderr
: "Le flux stderr est des barrettes de mémoire."stdout=subprocess.PIPE
etstderr=subprocess.STDOUT
. Notez que le mélange des deux flux de résultats en non-déterministes de sortie, et stdout va probablement devenir pleinement mises en mémoire tampon. Si vous avez le contrôle sur le processus secondaire de l'appel, vous pouvez désactiver la mise en mémoire tampon de là.data = proc.stderr.read()
dans le premier exemple de code bloque jusqu'à ce que tous de la lecture de données.read(1)
à l'origine, et expliquer les avantages et les inconvénients des différentes options... je ne sais pas pourquoi je l'ai changé. De toute façon, en expliquant les différentes options et les compromis à faire est plus important que de choisir l'un pour l'OP, sans explication...Je pense que ce que vous recherchez est quelque chose comme:
À la sortie/journal écrit dans un fichier, je voudrais modifier mon
cmdline
pour inclure d'habitude redirections, comme il le ferait sur une plaine linux bash/shell. Par exemple, je voudrais ajoutertee
à la ligne de commande:cmdline += ' | tee -a logfile.txt'
Espère que ça aide.
J'ai dû faire quelques modifications pour @abarnert réponse pour Python 3. Cela semble fonctionner: