Bash pipe à python

J’ai besoin d’absorber la sortie d’une commande bash via pipe en temps réel. Par exemple

for i in $(seq 1 4); do echo $i; sleep 1; done | ./script.py 

Où script.py a ceci

 for line in sys.stdin.readlines(): print line 

Je m’attends à ce que la séquence soit imprimée dès qu’elle est disponible, mais le script python attend que le script bash se termine avant de continuer.

J’ai regardé cette réponse, mais cela n’a pas résolu mon problème. Comment puis-je y arriver en python?

Le premier problème est que readlines lit toutes les lignes dans une liste. Il ne peut pas faire cela jusqu’à ce que toutes les lignes soient présentes, ce qui ne sera pas avant que stdin ait atteint EOF.

Mais vous n’avez pas vraiment besoin d’une liste des lignes, juste une partie des lignes pouvant être itérée . Et un fichier, comme sys.stdin , est déjà si itérable. Et c’est paresseux, cela génère une ligne à la fois dès qu’elles sont disponibles, au lieu d’attendre de les générer toutes en même temps.

Alors:

 for line in sys.stdin: print line 

Chaque fois que vous vous trouvez à chercher des readlines de readlines , demandez-vous si vous en avez vraiment besoin. La réponse sera toujours non. (Eh bien, sauf si vous voulez l’appeler avec un argument ou un object défectueux qui ne ressemble pas à un fichier.) Voir les lignes de lecture considérées comme idiotes pour plus d’informations.


Mais en attendant, il y a un deuxième problème. Ce n’est pas que Python met en mémoire tampon son stdin , ou que l’autre processus stocke sa stdout , mais que l’iterator d’object fichier lui-même effectue la mise en mémoire tampon interne (selon votre plate-forme mais sur la plupart des plates-formes POSIX). vous empêche d’arriver à la première ligne jusqu’à EOF, ou du moins jusqu’à ce que beaucoup de lignes aient été lues.

Ceci est un problème connu avec Python 2.x, qui a été corrigé dans 3.x, mais cela ne vous aide pas, sauf si vous êtes prêt à effectuer une mise à niveau.

La solution est mentionnée dans la documentation de la ligne de commande et de l’environnement, ainsi que dans la page de manuel sur la plupart des systèmes, mais au milieu de la documentation de l’indicateur -u :

Notez qu’il existe une mise en mémoire tampon interne dans xreadlines (), readlines () et les iterators de fichiers (“pour la ligne dans sys.stdin”) qui n’est pas influencée par cette option. Pour contourner ce problème, vous devrez utiliser “sys.stdin.readline ()” dans une boucle “while 1:”.

En d’autres termes:

 while True: line = sys.stdin.readline() if not line: break print line 

Ou:

 for line in iter(sys.stdin.readline, ''): print line 

Pour un problème différent, dans cette réponse , Alex Martelli souligne que vous pouvez toujours ignorer sys.stdin et re- fdopen le descripteur de fichier. Ce qui signifie que vous obtenez un wrapper autour d’un fd de POSIX au lieu d’un handle de stdio. Mais ce n’est ni nécessaire ni suffisant pour cette question, car le problème ne vient pas de la mise en mémoire tampon de la file.__iter__ , mais de la manière dont la file.__iter__ mémoire tampon du file.__iter__ interagit avec elle.


* Python 3.x n’utilise plus la mise en mémoire tampon de la bibliothèque de stdio; il fait tout lui-même, dans les types du module io , ce qui signifie que l’iterator peut simplement partager le même tampon que l’object fichier lui-même. Bien que io soit également disponible sur 2.x, ce n’est pas la méthode par défaut que vous obtenez pour open -or pour les descripteurs de fichiers stdio, c’est pourquoi cela ne vous aide pas ici.

Avec Python 2.7.9 (et probablement tous les Python antérieurs à 3.x), vous faites ce que vous attendez:

 #!/usr/bin/python import sys while True: line=sys.stdin.readline() if not line: break print line 

Vous pouvez aussi faire:

 #!/usr/bin/python import sys for line in iter(sys.stdin.readline, ''): print line 

Sur Python 3.4.3, vous pouvez faire ce que suggère abarnert:

 #!/usr/local/bin/python3 import sys for line in sys.stdin: print(line) 

Vous pouvez également rouvrir sys.stdin avec la classe io comme Python 3 utilise:

 #!/usr/bin/python import sys, io for line in io.open(sys.stdin.fileno()): print(line) 

Les 1ère, 2ème et dernière méthodes fonctionnent toutes sur Python 2.7.6 et 2.7.9 et Python 3.4.3 sur OS X; la troisième méthode, uniquement sur Python 3.

La réponse la plus populaire actuellement votée ne répond pas à la question car elle n’imprime pas la sortie lorsqu’elle est diffusée. Quelque chose comme le code ci-dessous devrait faire ce que vous voulez:

 import sys def readline(): while True: res = sys.stdin.readline() if not res: break yield res for line in readline(): print line 

Ici, plutôt que d’attendre que readlines construise une liste, nous lisons une seule ligne et nous cédons la valeur. Et nous continuons simplement à consumr des entrées et à céder jusqu’à ce que la fin du stream soit signalée par un retour vide de sys.stdin.readline ().