Öffnen Sie das Dokument mit der Standard-Betriebssystemanwendung in Python, sowohl unter Windows als auch unter Mac OS

125

Ich muss in der Lage sein, ein Dokument mit seiner Standardanwendung unter Windows und Mac OS zu öffnen. Grundsätzlich möchte ich dasselbe tun, was passiert, wenn Sie im Explorer oder Finder auf das Dokumentsymbol doppelklicken. Was ist der beste Weg, dies in Python zu tun?

Abdullah Jibaly
quelle
9
Es gab ein Problem dafür, dass dies ab 2008 in die Standardbibliothek des Python-Trackers aufgenommen wurde: bugs.python.org/issue3177
Ram Rachum

Antworten:

77

openund startsind Befehlsinterpreter für Mac OS / X bzw. Windows, um dies zu tun.

Um sie von Python aus aufzurufen, können Sie entweder das subprocessModul oder verwenden os.system().

Hier sind Überlegungen, welches Paket verwendet werden soll:

  1. Sie können sie über anrufen os.system, was funktioniert, aber ...

    Escaping: Funktioniert os.system nur mit Dateinamen, die keine Leerzeichen oder andere Shell-Metazeichen im Pfadnamen enthalten (z. B. A:\abc\def\a.txt), oder diese müssen maskiert werden. Es gibt shlex.quotefür Unix-ähnliche Systeme, aber nichts wirklich Standard für Windows. Vielleicht sehen Sie auch Python, Windows: Parsen von Befehlszeilen mit Shlex

    • Mac OS X: os.system("open " + shlex.quote(filename))
    • Windows: os.system("start " + filename)Wo eigentlich gesagt, filenamesollte auch entkommen.
  2. Sie können sie auch über ein subprocessModul aufrufen , aber ...

    Verwenden Sie für Python 2.7 und höher einfach

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

    In Python 3.5+ können Sie das etwas komplexere, aber auch etwas vielseitigere verwenden

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

    Wenn Sie bis zu Python 2.4 kompatibel sein müssen, können Sie subprocess.call()Ihre eigene Fehlerprüfung verwenden und implementieren:

    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
    

    Was sind nun die Vorteile der Verwendung subprocess?

    • Sicherheit: Theoretisch ist dies sicherer, aber tatsächlich müssen wir eine Befehlszeile auf die eine oder andere Weise ausführen. In beiden Umgebungen benötigen wir die Umgebung und die Dienste, um zu interpretieren, Pfade zu erhalten usw. In keinem Fall führen wir beliebigen Text aus, sodass es kein inhärentes 'filename ; rm -rf /'Problem gibt, aber Sie können eingeben. Wenn der Dateiname beschädigt werden kann, bietet die Verwendung von subprocess.callwenig Schutz.
    • Fehlerbehandlung : Es gibt uns eigentlich keine Fehlererkennung mehr, wir sind retcodein beiden Fällen immer noch abhängig von der ; Das Verhalten, im Falle eines Fehlers explizit eine Ausnahme auszulösen, hilft Ihnen sicherlich dabei, festzustellen, ob ein Fehler vorliegt (in einigen Szenarien ist ein Traceback jedoch möglicherweise nicht hilfreicher, als den Fehler einfach zu ignorieren).
    • Erzeugt einen (nicht blockierenden) Unterprozess : Wir müssen nicht auf den untergeordneten Prozess warten, da wir per Problemstellung einen separaten Prozess starten.

    Zum Einwand "Wird subprocessaber bevorzugt." Es os.system()ist jedoch nicht veraltet und in gewisser Weise das einfachste Werkzeug für diesen bestimmten Job. Fazit: Verwenden os.system()ist daher auch eine richtige Antwort.

    Ein deutlicher Nachteil ist, dass Sie für den Windows- startBefehl eine Übergabe benötigen , die shell=Truedie meisten Vorteile der Verwendung zunichte macht subprocess.

Charlie Martin
quelle
2
Je nachdem, woher filenamedie Form kommt, ist dies ein perfektes Beispiel dafür, warum os.system () unsicher und schlecht ist. Unterprozess ist besser.
Devin Jeanpierre
6
Nicks Antwort sah für mich gut aus. Nichts stand im Weg. Dinge mit falschen Beispielen zu erklären ist nicht leicht zu rechtfertigen.
Devin Jeanpierre
2
Es ist weniger sicher und weniger flexibel als die Verwendung von Unterprozessen. Das klingt für mich falsch.
Devin Jeanpierre
8
Natürlich ist es wichtig. Es ist der Unterschied zwischen einer guten und einer schlechten Antwort (oder einer schrecklichen Antwort). In den Dokumenten für os.system () selbst steht "Verwenden Sie das Unterprozessmodul". Was wird mehr benötigt? Das ist für mich genug Abwertung.
Devin Jeanpierre
20
Ich bin etwas zurückhaltend, diese Diskussion neu zu starten, aber ich denke, der Abschnitt "Späteres Update" macht es völlig falsch. Das Problem dabei os.system()ist, dass die Shell verwendet wird (und Sie hier keine Shell ausführen, sodass bei perfekt gültigen Dateinamen, die Shell-Metazeichen enthalten, schlechte Dinge passieren). Der Grund, warum subprocess.call()bevorzugt wird, ist, dass Sie die Option haben, die Shell mithilfe von zu umgehen subprocess.call(["open", filename]). Dies funktioniert für alle gültigen Dateinamen und führt auch bei nicht vertrauenswürdigen Dateinamen nicht zu einer Sicherheitsanfälligkeit durch Shell-Injection.
Sven Marnach
150

Verwenden Sie das subprocessunter Python 2.4+ verfügbare Modul nicht os.system(), damit Sie sich nicht mit dem Entkommen von Shell befassen müssen.

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))

Die doppelten Klammern sind, weil subprocess.call()eine Sequenz als erstes Argument gewünscht wird, daher verwenden wir hier ein Tupel. Auf Linux-Systemen mit Gnome gibt es auch einen gnome-openBefehl, der dasselbe tut, jedoch xdg-opender Free Desktop Foundation-Standard ist und in allen Linux-Desktop-Umgebungen funktioniert.

Nick
quelle
5
Die Verwendung von 'start' in subprocess.call () funktioniert unter Windows nicht - start ist nicht wirklich eine ausführbare Datei.
Tomas Sedovic
4
nitpick: auf allen linuxen (und ich denke die meisten BSDs) sollten Sie verwenden xdg-open- linux.die.net/man/1/xdg-open
gnud
6
Start unter Windows ist ein Shell-Befehl, keine ausführbare Datei. Sie können subprocess.call (('start', Dateipfad), shell = True) verwenden. Wenn Sie jedoch in einer Shell ausführen, können Sie auch os.system verwenden.
Peter Graham
Ich lief xdg-open test.pyund es öffnete sich der Firefox-Download-Dialog für mich. Was ist los mit dir? Ich bin auf Manjaro Linux.
Jason
1
@Jason Klingt so, als ob Ihre xdg-openKonfiguration verwirrt ist, aber das können wir in einem Kommentar nicht wirklich beheben . Vielleicht sehen Sie unix.stackexchange.com/questions/36380/…
Tripleee
44

Ich bevorzuge:

os.startfile(path, 'open')

Beachten Sie, dass dieses Modul Dateinamen unterstützt, deren Ordner und Dateien Leerzeichen enthalten, z

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

( Python-Dokumente ) 'Öffnen' muss nicht hinzugefügt werden (dies ist die Standardeinstellung). In den Dokumenten wird ausdrücklich erwähnt, dass dies einem Doppelklick auf das Symbol einer Datei im Windows Explorer entspricht.

Diese Lösung ist nur Windows.

DrBloodmoney
quelle
Vielen Dank. Ich habe die Verfügbarkeit nicht bemerkt, da sie in den Dokumenten an den letzten Absatz angehängt ist. In den meisten anderen Abschnitten belegt der Verfügbarkeitsvermerk eine eigene Zeile.
DrBloodmoney
Unter Linux ist die startfileFunktion aus irgendeinem Grund nicht vorhanden, anstatt einen Fehler auszulösen. Dies bedeutet, dass Benutzer eine verwirrende Fehlermeldung über eine fehlende Funktion erhalten. Möglicherweise möchten Sie die Plattform überprüfen, um dies zu vermeiden.
cz
39

Der Vollständigkeit halber (es war nicht in Frage) wird xdg-open unter Linux dasselbe tun.

dF.
quelle
6
+1 Normalerweise sollten Antwortende keine Fragen beantworten, die nicht gestellt wurden, aber in diesem Fall denke ich, dass dies für die gesamte SO-Community sehr relevant und hilfreich ist.
Demongolem
suchte danach
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
quelle
2
Huh, ich wusste nichts über Startdatei. Es wäre schön, wenn die Mac- und Linux-Versionen von Python eine ähnliche Semantik aufgreifen würden.
Nick
3
Relevanter Python-Fehler: bugs.python.org/issue3177 - Bereitstellung eines schönen Patches, der möglicherweise akzeptiert wird =)
gnud
xdg-open Befehl für Linux
TheTechRobo36414519
21

Wenn Sie eine heuristische Methode verwenden müssen, können Sie dies in Betracht ziehen webbrowser.
Es ist eine Standardbibliothek und würde trotz ihres Namens auch versuchen, Dateien zu öffnen:

Beachten Sie, dass auf einigen Plattformen der Versuch, einen Dateinamen mit dieser Funktion zu öffnen, möglicherweise funktioniert und das zugehörige Programm des Betriebssystems startet. Dies wird jedoch weder unterstützt noch portabel. ( Referenz )

Ich habe diesen Code ausprobiert und er hat unter Windows 7 und Ubuntu Natty einwandfrei funktioniert:

import webbrowser
webbrowser.open("path_to_file")

Dieser Code funktioniert auch in Windows XP Professional mit Internet Explorer 8 einwandfrei.

etuardu
quelle
3
Soweit ich das beurteilen kann, ist dies bei weitem die beste Antwort. Scheint plattformübergreifend zu sein und es muss nicht überprüft werden, welche Plattform verwendet wird, oder es muss eine Plattform importiert werden.
Polandeer
2
@ Jonathanrocher: Ich sehe Mac-Unterstützung im Quellcode . Es wird open locationdort verwendet, was funktionieren sollte, wenn Sie den Pfad als gültige URL angeben.
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 "Beachten Sie, dass auf einigen Plattformen der Versuch, einen Dateinamen mit [webbrowser.open (url)] zu öffnen, möglicherweise funktioniert und das zugehörige Programm des Betriebssystems startet Dies wird weder unterstützt noch ist es portabel. "
Nyanpasu64
6

Wenn Sie den subprocess.call()Weg gehen möchten , sollte es unter Windows folgendermaßen aussehen:

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

Sie können nicht einfach verwenden:

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

weil start ist keine ausführbare Datei, sondern ein Befehl des cmd.exeProgramms. Das funktioniert:

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

aber nur, wenn der FILE_NAME keine Leerzeichen enthält.

Während subprocess.callmethod en die Parameter korrekt zitiert, hat der startBefehl eine ziemlich seltsame Syntax, wobei:

start notes.txt

macht etwas anderes als:

start "notes.txt"

Die erste Zeichenfolge in Anführungszeichen sollte den Titel des Fensters festlegen. Damit es mit Räumen funktioniert, müssen wir Folgendes tun:

start "" "my notes.txt"

Das ist, was der Code oben tut.

Tomas Sedovic
quelle
5

Start unterstützt keine langen Pfadnamen und Leerzeichen. Sie müssen es in 8.3-kompatible Pfade konvertieren.

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 )

Die Datei muss vorhanden sein, um mit dem API-Aufruf arbeiten zu können.

bFloch
quelle
1
Eine andere Problemumgehung besteht darin, ihm einen Titel in Anführungszeichen zu geben, z. B.start "Title" "C:\long path to\file.avi"
user3364825
3

Ich bin ziemlich spät dran, aber hier ist eine Lösung mit der Windows-API. Dies öffnet immer die zugehörige Anwendung.

import ctypes

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

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

Viele magische Konstanten. Die erste Null ist die hwnd des aktuellen Programms. Kann Null sein. Die anderen beiden Nullen sind optionale Parameter (Parameter und Verzeichnis). 5 == SW_SHOW, gibt an, wie die App ausgeführt werden soll. Weitere Informationen finden Sie in den ShellExecute-API-Dokumenten .

George
quelle
1
wie ist es im Vergleich zu os.startfile(file)?
JFS
2

Auf Mac OS können Sie "Öffnen" nennen.

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

Dies würde die Datei mit TextEdit oder einer anderen App öffnen, die als Standard für diesen Dateityp festgelegt ist

lcvinny
quelle
2

Wenn Sie die App angeben möchten, mit der die Datei unter Mac OS X geöffnet werden soll, verwenden Sie Folgendes: os.system("open -a [app name] [file name]")


quelle
2

Unter Windows 8.1 haben die folgenden Funktionen funktioniert, während andere vorgegebene Methoden mit subprocess.callFehlern mit Pfad Leerzeichen enthalten.

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

Wenn Sie diese und die Antworten anderer zuvor verwenden, finden Sie hier einen Inline-Code, der auf mehreren Plattformen funktioniert.

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
quelle
2

os.startfile(path, 'open')unter Windows ist gut, weil, wenn Leerzeichen im Verzeichnis vorhanden sind, os.system('start', path_name)die App nicht korrekt geöffnet werden kann und wenn der i18n im Verzeichnis vorhanden ist, os.systemder Unicode in den Codec der Konsole in Windows geändert werden muss.

BearPy
quelle