Ouvrir le document avec l'application OS par défaut en Python, à la fois sous Windows et Mac OS

126

J'ai besoin de pouvoir ouvrir un document en utilisant son application par défaut sous Windows et Mac OS. Fondamentalement, je veux faire la même chose que lorsque vous double-cliquez sur l'icône du document dans l'Explorateur ou le Finder. Quelle est la meilleure façon de faire cela en Python?

Abdullah Jibaly
la source
9
Il y a eu un problème pour que cela soit inclus dans la bibliothèque standard du tracker Python à partir de 2008: bugs.python.org/issue3177
Ram Rachum

Réponses:

77

openet startsont des choses d'interpréteur de commandes pour Mac OS / X et Windows respectivement, pour ce faire.

Pour les appeler depuis Python, vous pouvez utiliser le subprocessmodule ou os.system().

Voici quelques considérations sur le package à utiliser:

  1. Vous pouvez les appeler via os.system, ce qui fonctionne, mais ...

    Escaping: os.system fonctionne uniquement avec les noms de fichiers qui n'ont pas d'espaces ou d'autres métacaractères shell dans le chemin d'accès (par exemple A:\abc\def\a.txt), sinon ceux-ci doivent être échappés. Il y en a shlex.quotepour les systèmes de type Unix, mais rien de vraiment standard pour Windows. Peut-être voir aussi python, windows: analyse des lignes de commande avec shlex

    • Mac OS X: os.system("open " + shlex.quote(filename))
    • Windows: os.system("start " + filename)là où proprement dit filenamedoit être échappé aussi.
  2. Vous pouvez également les appeler via le subprocessmodule, mais ...

    Pour Python 2.7 et plus récent, utilisez simplement

    subprocess.check_call(['open', filename])

    Dans Python 3.5+, vous pouvez utiliser de manière équivalente le légèrement plus complexe mais aussi un peu plus polyvalent

    subprocess.run(['open', filename], check=True)

    Si vous avez besoin d'être compatible jusqu'à Python 2.4, vous pouvez utiliser subprocess.call()et implémenter votre propre vérification d'erreur:

    try:
        retcode = subprocess.call("open " + filename, shell=True)
        if retcode < 0:
            print >>sys.stderr, "Child was terminated by signal", -retcode
        else:
            print >>sys.stderr, "Child returned", retcode
    except OSError, e:
        print >>sys.stderr, "Execution failed:", e
    

    Maintenant, quels sont les avantages de l'utilisation subprocess?

    • Sécurité: en théorie, c'est plus sûr, mais en fait, nous devons exécuter une ligne de commande dans un sens ou dans l'autre; dans l'un ou l'autre environnement, nous avons besoin de l'environnement et des services pour interpréter, trouver des chemins, etc. Dans les deux cas, nous n'exécutons pas de texte arbitraire, il n'a donc pas de 'filename ; rm -rf /'problème inhérent "mais vous pouvez taper ", et si le nom de fichier peut être corrompu, utiliser subprocess.callnous donne peu de protection supplémentaire.
    • Gestion des erreurs: Cela ne nous donne en fait plus de détection d'erreur, nous dépendons toujours de la retcodedans les deux cas; mais le comportement pour déclencher explicitement une exception en cas d'erreur vous aidera certainement à remarquer s'il y a un échec (bien que dans certains scénarios, un retraçage ne soit pas du tout plus utile que d'ignorer simplement l'erreur).
    • Génère un sous-processus (non bloquant) : nous n'avons pas besoin d'attendre le processus enfant, car nous commençons par une déclaration de problème un processus séparé.

    À l'objection «mais subprocessest préféré». Cependant, os.system()n'est pas obsolète, et c'est en quelque sorte l'outil le plus simple pour ce travail particulier. Conclusion: utiliser os.system()est donc aussi une bonne réponse.

    Un inconvénient majeur est que la startcommande Windows vous oblige à passer shell=Truece qui annule la plupart des avantages de l'utilisation subprocess.

Charlie Martin
la source
2
Selon d'où filenamevient la forme, c'est un exemple parfait de pourquoi os.system () n'est pas sécurisé et est mauvais. le sous-processus est meilleur.
Devin Jeanpierre
6
La réponse de Nick me paraissait bien. Rien ne l'a gêné. Expliquer les choses en utilisant de mauvais exemples n'est pas facilement justifiable.
Devin Jeanpierre
2
C'est moins sûr et moins flexible que l'utilisation de sous-processus. Cela me semble faux.
Devin Jeanpierre
8
Bien sûr, cela compte. C'est la différence entre une bonne réponse et une mauvaise réponse (ou une réponse terrible). La documentation pour os.system () dit elle-même «Utilisez le module de sous-processus». Que faut-il de plus? C'est assez de dépréciation pour moi.
Devin Jeanpierre
20
Je me sens un peu réticent à recommencer cette discussion, mais je pense que la section "Mise à jour ultérieure" est complètement erronée. Le problème avec os.system()est qu'il utilise le shell (et vous ne faites aucun shell qui s'échappe ici, donc de mauvaises choses se produiront pour des noms de fichiers parfaitement valides qui contiennent des méta-caractères du shell). La raison pour laquelle subprocess.call()est préféré est que vous avez la possibilité de contourner le shell en utilisant subprocess.call(["open", filename]). Cela fonctionne pour tous les noms de fichiers valides et n'introduit pas de vulnérabilité d'injection shell, même pour les noms de fichiers non approuvés.
Sven Marnach
151

Utilisez le subprocessmodule disponible sur Python 2.4+, non os.system(), pour ne pas avoir à gérer l'échappement du shell.

import subprocess, os, platform
if platform.system() == 'Darwin':       # macOS
    subprocess.call(('open', filepath))
elif platform.system() == 'Windows':    # Windows
    os.startfile(filepath)
else:                                   # linux variants
    subprocess.call(('xdg-open', filepath))

Les doubles parenthèses sont parce que subprocess.call()veut une séquence comme premier argument, nous utilisons donc un tuple ici. Sur les systèmes Linux avec Gnome, il existe également une gnome-opencommande qui fait la même chose, mais qui xdg-openest le standard de Free Desktop Foundation et fonctionne dans les environnements de bureau Linux.

pseudo
la source
5
L'utilisation de 'start' dans subprocess.call () ne fonctionne pas sous Windows - start n'est pas vraiment un exécutable.
Tomas Sedovic
4
nitpick: sur tous les Linux (et je suppose que la plupart des BSD), vous devriez utiliser xdg-open- linux.die.net/man/1/xdg-open
gnud
6
démarrer sous Windows est une commande shell, pas un exécutable. Vous pouvez utiliser subprocess.call (('start', filepath), shell = True), bien que si vous exécutez dans un shell, vous pouvez également utiliser os.system.
Peter Graham
J'ai couru xdg-open test.pyet il a ouvert la boîte de dialogue de téléchargement de Firefox pour moi. Qu'est-ce qui ne va pas? Je suis sur manjaro linux.
Jason le
1
@Jason On dirait que votre xdg-openconfiguration est confuse, mais ce n'est pas vraiment quelque chose que nous pouvons résoudre dans un commentaire. Peut-être voir unix.stackexchange.com/questions/36380/…
tripleee
44

Je préfère:

os.startfile(path, 'open')

Notez que ce module prend en charge les noms de fichiers qui ont des espaces dans leurs dossiers et fichiers, par exemple

A:\abc\folder with spaces\file with-spaces.txt

( python docs ) 'open' n'a pas besoin d'être ajouté (c'est la valeur par défaut). La documentation mentionne spécifiquement que cela revient à double-cliquer sur l'icône d'un fichier dans l'Explorateur Windows.

Cette solution concerne uniquement Windows.

DrBloodmoney
la source
Merci. Je n'ai pas remarqué la disponibilité, car la documentation l'a ajoutée au dernier paragraphe. Dans la plupart des autres sections, la note de disponibilité occupe sa propre ligne.
DrBloodmoney
Sous Linux pour une raison quelconque, plutôt que de générer une erreur, la startfilefonction n'existe même pas, ce qui signifie que les utilisateurs recevront un message d'erreur déroutant à propos d'une fonction manquante. Vous voudrez peut-être vérifier la plate-forme pour éviter cela.
cz
39

Juste pour être complet (ce n'était pas dans la question), xdg-open fera de même sous Linux.

dF.
la source
6
+1 Habituellement, les répondants ne devraient pas répondre aux questions qui n'ont pas été posées, mais dans ce cas, je pense que c'est très pertinent et utile pour la communauté SO dans son ensemble.
demongolem
recherchait ceci
nurettin
25
import os
import subprocess

def click_on_file(filename):
    '''Open document with default application in Python.'''
    try:
        os.startfile(filename)
    except AttributeError:
        subprocess.call(['open', filename])
nosklo
la source
2
Huh, je ne connaissais pas startfile. Ce serait bien si les versions Mac et Linux de Python reprenaient une sémantique similaire.
Nick
3
Bogue python pertinent: bugs.python.org/issue3177 - fournit un correctif sympa, et il pourrait être accepté =)
gnud
Commande xdg-open pour linux
TheTechRobo36414519
21

Si vous devez utiliser une méthode heuristique, vous pouvez envisager webbrowser.
C'est une bibliothèque standard et malgré son nom, elle essaierait également d'ouvrir des fichiers:

Notez que sur certaines plates-formes, essayer d'ouvrir un nom de fichier à l'aide de cette fonction peut fonctionner et démarrer le programme associé au système d'exploitation. Cependant, cela n'est ni pris en charge ni portable. ( Référence )

J'ai essayé ce code et cela a bien fonctionné sous Windows 7 et Ubuntu Natty:

import webbrowser
webbrowser.open("path_to_file")

Ce code fonctionne également correctement dans Windows XP Professionnel, en utilisant Internet Explorer 8.

etuardu
la source
3
Pour autant que je sache, c'est de loin la meilleure réponse. Semble multi-plateforme et pas besoin de vérifier quelle plate-forme est utilisée ou d'importer os, plate-forme.
polandeer
2
@jonathanrocher: Je vois le support Mac dans le code source . Il utilise open locationlà-bas qui devrait fonctionner si vous donnez le chemin comme une URL valide.
jfs
1
macOS:import webbrowser webbrowser.open("file:///Users/nameGoesHere/Desktop/folder/file.py")
Daniel Springer
3
docs.python.org/3/library/webbrowser.html#webbrowser.open "Notez que sur certaines plates-formes, essayer d'ouvrir un nom de fichier à l'aide de [webbrowser.open (url)] peut fonctionner et démarrer le programme associé au système d'exploitation. Cependant , ce n'est ni pris en charge ni portable. "
nyanpasu64
6

Si vous voulez suivre le subprocess.call()chemin, cela devrait ressembler à ceci sous Windows:

import subprocess
subprocess.call(('cmd', '/C', 'start', '', FILE_NAME))

Vous ne pouvez pas simplement utiliser:

subprocess.call(('start', FILE_NAME))

car ce start n'est pas un exécutable mais une commande du cmd.exeprogramme. Cela marche:

subprocess.call(('cmd', '/C', 'start', FILE_NAME))

mais uniquement s'il n'y a pas d'espaces dans le FILE_NAME.

Alors que la subprocess.callméthode en cite correctement les paramètres, la startcommande a une syntaxe plutôt étrange, où:

start notes.txt

fait autre chose que:

start "notes.txt"

La première chaîne entre guillemets doit définir le titre de la fenêtre. Pour le faire fonctionner avec les espaces, nous devons faire:

start "" "my notes.txt"

c'est ce que fait le code en haut.

Tomas Sedovic
la source
5

Start ne prend pas en charge les noms de chemin longs et les espaces blancs. Vous devez le convertir en chemins compatibles 8.3.

import subprocess
import win32api

filename = "C:\\Documents and Settings\\user\\Desktop\file.avi"
filename_short = win32api.GetShortPathName(filename)

subprocess.Popen('start ' + filename_short, shell=True )

Le fichier doit exister pour fonctionner avec l'appel d'API.

bFloch
la source
1
Une autre solution consiste à lui donner un titre entre guillemets, par exemplestart "Title" "C:\long path to\file.avi"
user3364825
3

Je suis assez tard dans le lot, mais voici une solution utilisant l'API Windows. Cela ouvre toujours l'application associée.

import ctypes

shell32 = ctypes.windll.shell32
file = 'somedocument.doc'

shell32.ShellExecuteA(0,"open",file,0,0,5)

Beaucoup de constantes magiques. Le premier zéro est le hwnd du programme en cours. Peut être zéro. Les deux autres zéros sont des paramètres optionnels (paramètres et répertoire). 5 == SW_SHOW, il spécifie comment exécuter l'application. Lisez la documentation de l'API ShellExecute pour plus d'informations.

George
la source
1
comment ça se compare os.startfile(file)?
jfs
2

sur mac os, vous pouvez appeler «ouvert»

import os
os.popen("open myfile.txt")

cela ouvrirait le fichier avec TextEdit, ou n'importe quelle application définie par défaut pour ce type de fichier

lcvinny
la source
2

Si vous souhaitez spécifier l'application avec laquelle ouvrir le fichier sous Mac OS X, utilisez ceci: os.system("open -a [app name] [file name]")


la source
2

Sur Windows 8.1, ci-dessous ont fonctionné alors que d'autres méthodes données avec subprocess.calléchec avec chemin contiennent des espaces.

subprocess.call('cmd /c start "" "any file path with spaces"')

En utilisant ceci et les réponses d'autres auparavant, voici un code en ligne qui fonctionne sur plusieurs plates-formes.

import sys, os, subprocess
subprocess.call(('cmd /c start "" "'+ filepath +'"') if os.name is 'nt' else ('open' if sys.platform.startswith('darwin') else 'xdg-open', filepath))
Ch.Idea
la source
2

os.startfile(path, 'open')sous Windows, c'est bien car lorsque des espaces existent dans le répertoire, os.system('start', path_name)ne peut pas ouvrir l'application correctement et lorsque l'i18n existe dans le répertoire, il os.systemfaut changer l'unicode en codec de la console sous Windows.

BearPy
la source