On m'a conseillé ici de changer mes instructions os.popen pour subprocess.Popen. Ayant lu un peu de doc depuis, j'en comprends maintenant mieux l'intérêt. J'aurais cependant quelques questions à poser.
Prenons le code suivant par exemple. Mettre la variable shell à true, ça veut dire que les variables comme $HOME vont être traitées, et retourner la valeur du système ? Par exemple chez moi, /home/djipey, c'est bien ça ?
Mais mon plus gros problème est l'écriture dans ma commande système. Prenons le code suivant:
Il me semble que si le paramètre args de Popen est une chaîne, tu ne peux pas exécuter une commande avec paramètres.
Pour cela ça doit être une séquence, c'est à dire que ta commande doit être splittée, ce qui donnera une liste composée de ta commande et de tes paramètres.
Maintenant je suis pas sûr et j'ai pas le temps de vérifier.
Avec shell=True, subprocess passe la chaîne telle qu'elle est écrite dans le programme au shell du système, donc cmd.exe sur Windows ou bash/zsh/… sous Linux. Ce qui permet d'utiliser les spécificités du shell, par exemple les opérateurs comme le * (extension), le | (qui permet de diriger la sortie d'une commande vers l'entrée d'une autre) ou les variables, comme tu le dis. Mais souvent, on peut se passer de tout cela et il vaut alors mieux prendre l'habitude de ne pas utiliser ce shell=True, car il peut être source de problèmes et/ou de failles de sécurité, selon l'application.
Quand on utilise shell=False (ou si on ne le précise pas), la commande n'est plus traitée par le shell et il faut fournir à subprocess une liste dont le premier élément est l'exécutable à appeler (si on lui donne une chaîne cat -n file.txt, subprocess cherchera un exécutable portant le nom cat -n file.txt au lieu de cat, d'où la nécessité de passer par une liste).
À préciser d'où va venir le flux d'entrée du processus fils.
En gros, en jouant sur les attributs stdin et stdout de l'objet Popen, tu peux envoyer ce que tu veux sur l'entrée standard d'un processus, récupérer sa sortie standard sur une chaîne de caractères, rediriger sa sortie d'erreur, ou créer des pipes, pour rediriger la sortie d'un processus fils vers l'entrée d'un autre.
Oui, je me doutais que ça servait d'entrée pour la commande. Ce que je me demandais, c'est comment l'utiliser. Parce que si je comprends bien, en passant la liste d'arguments de mon dernier post à Popen, je n'ai pas besoin de stdin. Et dans le cas d'arguments qui ne varient pas, j'en vois encore moins l'intérêt.
Oui, je me doutais que ça servait d'entrée pour la commande. Ce que je me demandais, c'est comment l'utiliser. Parce que si je comprends bien, en passant la liste d'arguments de mon dernier post à Popen, je n'ai pas besoin de stdin. Et dans le cas d'arguments qui ne varient pas, j'en vois encore moins l'intérêt.
Ben si tu n'en as pas besoin, ne l'utilise pas.
Il suffit de ne rien spécifier, l'argument stdin est à None par défaut.
C'est complètement décorrélé des arguments qui varient ou non. Tu peux très bien avoir une commande qui sera toujours, par exemple, ssh host 'cat > /some/file.data' à laquelle tu envoies du contenu via STDIN pour qu'elles soient écrites sur un fichier sur un hôte distant over SSH, par exemple. Les arguments ne changeront jamais, mais les données, oui.
Sinon pour répondre à ta question :
djipey a écrit:
Est-ce que je peux/dois donc utiliser la liste:
['ffmpegthumbnailer', '-i', path, '-o', name + '_thumb.png']
?
Voilà.
Sinon tu peux toujours passer ta chaîne toute faite à la fonction shlex.split() si tu as un doute sur la façon dont les arguments doivent être séparés.
D'accord. J'ai donc commencé ma réécriture de code avec vos précisions:
def caps(path):
""" Fct permettant de créer le cap d'un fichier """
name = path.split("/")[-1]
percent = 5
while percent < 100:
#TODO: option pr gérer la taille des images
#TODO: mettre les miniatures temporaires ds un dossier temp
#On génère des miniatures à intervalle de % régulier, avec une largeur particulière
try:
#make_pictures = os.popen("ffmpegthumbnailer -s 256 -i \"{0}\" -o cap-{1}.png -t {2} > /dev/null".format(path, percent, percent))
cmd = subprocess.Popen(['ffmpegthumbnailer', '-s', '256', '-i',
path, '-o', 'cap-' + str(percent) + '.png', '-t', percent],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout_value = cmd.communicate()
percent += 10
#make_pictures.read()
except:
print("Erreur lors de la génération de la mosaïque")
pass
Sauf que ça lève une exception, assez bizarre:
Traceback (most recent call last):
^C File "/home/djipey/informatique/python/bibli/screen.py", line 43, in caps
File "/usr/lib/python3.3/subprocess.py", line 784, in __init__
File "/usr/lib/python3.3/subprocess.py", line 1271, in _get_handles
OSError: [Errno 24] Trop de fichiers ouverts
Tu ne wait() pas tes processus fils, donc ceux-ci ne se terminent jamais vraiment et restent à l'état de zombies (et ne libèrent donc pas leurs ressources). Au bout d'un moment, le nombre de fichiers ouverts par ces processus fils dépasse la limite allouée par le noyau.
Note qu'il est probable que cette erreur soit la conséquence du fait que d'autres programmes consommateurs (et gourmands) en fichiers soient en train de tourner sur la machine, comme MySQL ou un client bittorrent.
Tu peux augmenter cette limite en te servant du fichier /proc/sys/fs/file-max.
J'ai rien dit, communicate() implique un wait. Il n'y a donc aucune raison que ce genre de trucs se produise spécifiquement sur ton code. C'est peut-être une autre couille sur ton système, indépendante de ce script.
Essaye de voir le nombre de file handles déjà ouverts sur ton système avec un lsof |wc -l, et compare cette valeur avec celle de cat /proc/sys/fs/file-max ?
J'avais pas vu que tu avais posté entre temps, mais j'ai édité : tu devrais virer les arguments stdout et stderr du constructeur de Popen. Tu ne te sers nulle part des pipes qui sont créés (et qui sont eux aussi représentés par des file descriptors sous Linux).
Ça ne résout pas le problème, j'avais déjà essayé. J'ai essayé d'enlever les try et j'ai une autre exception,bien bizarre elle aussi, vu qu'aucune ligne n'est indiquée comme générant une erreur:
File "/home/djipey/informatique/python/bibli/gui.py", line 157, in verification
liste.verification(self.options, self.majProgression)
File "/home/djipey/informatique/python/bibli/liste.py", line 298, in verification
importation(each_dossier, callback)
File "/home/djipey/informatique/python/bibli/liste.py", line 439, in importation
screen.caps(path)
File "/home/djipey/informatique/python/bibli/screen.py", line 45, in caps
path, '-o', 'cap-' + str(percent) + '.png', '-t', percent])
File "/usr/lib/python3.3/subprocess.py", line 818, in __init__
restore_signals, start_new_session)
File "/usr/lib/python3.3/subprocess.py", line 1363, in _execute_child
restore_signals, start_new_session, preexec_fn)
TypeError: Can't convert 'int' object to str implicitly
J'ai essayé d'enlever les try et j'ai une autre exception,bien bizarre elle aussi, vu qu'aucune ligne n'est indiquée comme générant une erreur:
Si. Ici :
File "/home/djipey/informatique/python/bibli/screen.py", line 45, in caps
path, '-o', 'cap-' + str(percent) + '.png', '-t', percent])
Le dernier argument dans ta liste est supposé être une chaîne de caractères, alors que c'est un int.
Pour le coup ton problème de base (avec les filehandles) peut aussi être un effet de bord vicieux de celui-là, occulté par un except: trop large (si tu utilises un try/except, précise TOUJOURS le type d'erreur que tu t'attends à rattraper, ça évite au programme de continuer à tourner en dépit d'une erreur critique de ce genre), et empiré par les PIPES que tu ouvres pour rien.
Du coup j'ai rajouté un FileNotFoundError comme exception.
Par contre, juste pour ma culture. Prenons par exemple l'autre bout de ma fonction:
try:
#On construit une mosaique ac les images générées précédemment. 1 pixel entre chaque image.
#2 images de large sur 5 de haut.
#http://dptnt.com/2008/12/create-photo-montage-with-imagemagick/
mosaic = os.popen("montage -geometry +1+1 -tile 2x5 cap-*.png \"{0}\"_mosaic.png > /dev/null".format(name))
mosaic.read()
#On déplace la mosaique dans le dossier des images
os.renames("{0}/{1}_mosaic.png".format(os.getcwd(), name), "{0}/screens/{1}_mosaic.png".format(os.getcwd(), name) )
#On supprime les images séparées constituant la mosaique
for fichier in os.listdir(os.getcwd()):
#Module fnmatch, qui permet de faire des recherches
#façon unix
if fnmatch.fnmatch(fichier, 'cap-*.png'):
os.remove(fichier)
except:
print("La création de la mosaïque a échoué. Retour.")
return
(on oublie les os.popen, je vais les changer). Là encore, j'ai du mal à voir ce que je spécifie comme exception. À part le fileNotFound, il peut se passer plein de trucs je pense. Comment tu gèrerais ça ?
Et les pipes, je voudrais m'en servir un jour peut-être, pour des logs ou je ne sais quoi. Par exemple, là, je ne pourrais pas m'en servir pour afficher ce qu'il s'est mal passé ?
Oui, tu avais raison, c'était bien ça. Et pour l'exception, je suis d'accord. J'ai codé ça il y a quelques temps déjà, j'ai un peu progressé depuis
Faut avouer que c'était pas un bug facile à diagnostiquer, mais ça illustre bien le problème des except:. Je vais garder ce topic comme référence pour la prochaine fois que je devrai expliquer pourquoi il faut toujours préciser ce qu'on cherche à rattraper : c'est presque un cas d'école !
djipey a écrit:
Là encore, j'ai du mal à voir ce que je spécifie comme exception. À part le fileNotFound, il peut se passer plein de trucs je pense. Comment tu gèrerais ça ?
Je commencerais par virer le except: et puis je ferais tourner le script dans plein de conditions susceptibles de planter. Chaque fois qu'une exception n'est pas rattrapée, je déciderais si c'est quelque chose qu'il faut rattraper (une erreur "normale") ou bien plutôt un bug dû au code. Dans le premier cas, je rajouterais à chaque fois le except Truc: correspondant avec un message d'erreur qui fasse office de diagnostic pour l'utilisateur ("This file is missing", "Please check /proc/sys/[truc]", etc.).
Et les pipes, je voudrais m'en servir un jour peut-être, pour des logs ou je ne sais quoi. Par exemple, là, je ne pourrais pas m'en servir pour afficher ce qu'il s'est mal passé ?
Oui, si tu veux récupérer ce qui s'est mal passé et utiliser ces données, alors il faut setter stdout et stderr à subprocess.PIPE. Là je te les ai fait virer parce que dans la suite tu ne t'en servais pas.
Merci à toi pour les astuces. Au final, je suis arrivé à quelque chose de potable pour l'instant, bien que je n'ai pas encore eu l'occasion de beaucoup tester:
def caps(path):
""" Fct permettant de créer le cap d'un fichier """
name = path.split("/")[-1]
percent = 5
while percent < 100:
#TODO: option pr gérer la taille des images
#TODO: mettre les miniatures temporaires ds un dossier temp
#On génère des miniatures à intervalle de % régulier, avec une largeur particulière
try:
cmd = subprocess.Popen(['ffmpegthumbnailer', '-s', '256', '-i',
path, '-o', 'cap-' + str(percent) + '.png', '-t', str(percent)],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = cmd.communicate()
if cmd.returncode != 0:
print("La commande ffmpegthumbnailer s'est mal passée. La mosaïque n'a pas été générée pour {0}".format(path))
return
except FileNotFoundError:
print("La création de la mosaïque a échoué, le fichier n'existe pas.")
return
percent += 10
Si la commande s'est mal passé, le code de sortie n'est pas bon, et je peux gérer au moins cette partie. Et je ne spécifie qu'une exception, au moins les autres seront visibles en cas de problème.
Merci à toi
http://maymayhem.fr/
[subprocess] Question sur le module
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.