Otwórz dokument w domyślnej aplikacji systemu operacyjnego w języku Python, zarówno w systemie Windows, jak i Mac OS

126

Muszę móc otworzyć dokument przy użyciu domyślnej aplikacji w systemie Windows i Mac OS. Zasadniczo chcę zrobić to samo, co dzieje się po dwukrotnym kliknięciu ikony dokumentu w Eksploratorze lub Finderze. Jaki jest najlepszy sposób na zrobienie tego w Pythonie?

Abdullah Jibaly
źródło
9
Wystąpił problem z dołączeniem tego do standardowej biblioteki w trackerze
Ram Rachum

Odpowiedzi:

77

openi startsą interpreterami poleceń odpowiednio dla Mac OS / X i Windows, aby to zrobić.

Aby wywołać je z Pythona, możesz użyć subprocessmodule lub os.system().

Oto uwagi dotyczące tego, którego pakietu użyć:

  1. Możesz do nich zadzwonić os.system, co działa, ale ...

    Escaping: os.system działa tylko z nazwami plików, które nie mają żadnych spacji ani innych metaznaków powłoki w nazwie ścieżki (np. A:\abc\def\a.txt), Albo wymagają one zmiany znaczenia. Jest shlex.quotedla systemów typu Unix, ale nie ma nic standardowego dla Windows. Może zobacz także python, windows: parsing wiersza poleceń za pomocą shlex

    • Mac OS X: os.system("open " + shlex.quote(filename))
    • Windows: os.system("start " + filename)tam, gdzie filenamenależy uciec od prawidłowego mówienia .
  2. Możesz też zadzwonić do nich przez subprocessmoduł, ale ...

    W przypadku Pythona 2.7 i nowszych po prostu użyj

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

    W Pythonie 3.5+ możesz równoważnie użyć nieco bardziej złożonego, ale też nieco bardziej wszechstronnego

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

    Jeśli chcesz zachować zgodność aż do Pythona 2.4, możesz użyć subprocess.call()i zaimplementować własne sprawdzanie błędów:

    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

    Jakie są zalety używania subprocess?

    • Bezpieczeństwo: Teoretycznie jest to bezpieczniejsze, ale w rzeczywistości musimy wykonać wiersz poleceń w taki czy inny sposób; W każdym środowisku potrzebujemy środowiska i usług do interpretacji, uzyskiwania ścieżek i tak dalej. W żadnym przypadku nie wykonujemy dowolnego tekstu, więc nie ma on nieodłącznego 'filename ; rm -rf /'problemu „ale możesz wpisać ”, a jeśli nazwa pliku może być uszkodzona, użycie nie subprocess.calldaje nam dodatkowej ochrony.
    • Obsługa błędów: tak naprawdę nie daje nam więcej możliwości wykrywania błędów, nadal polegamy na retcodeobu przypadkach; ale zachowanie polegające na jawnym zgłaszaniu wyjątku w przypadku błędu z pewnością pomoże ci zauważyć, jeśli wystąpi awaria (chociaż w niektórych scenariuszach śledzenie może wcale nie być bardziej pomocne niż po prostu zignorowanie błędu).
    • Odradza podproces (nieblokujący) : nie musimy czekać na proces potomny, ponieważ zgodnie z opisem problemu rozpoczynamy oddzielny proces.

    Na zastrzeżenie „Ale subprocessjest preferowane”. Jednak os.system()nie jest przestarzałe i jest w pewnym sensie najprostszym narzędziem do tego konkretnego zadania. Wniosek: używanie os.system()jest zatem również poprawną odpowiedzią.

    Znaczącą wadą jest to, że startpolecenie systemu Windows wymaga przejścia, shell=Trueco neguje większość korzyści wynikających z używania subprocess.

Charlie Martin
źródło
2
W zależności od filenameformy, jest to doskonały przykład tego, dlaczego os.system () jest niepewne i złe. podproces jest lepszy.
Devin Jeanpierre
6
Odpowiedź Nicka wyglądała dobrze. Nic nie przeszkadzało. Wyjaśnianie rzeczy na błędnych przykładach nie jest łatwe do usprawiedliwienia.
Devin Jeanpierre
2
Jest mniej bezpieczny i mniej elastyczny niż używanie podprocesu. Dla mnie to brzmi źle.
Devin Jeanpierre
8
Oczywiście to ma znaczenie. To różnica między dobrą odpowiedzią a złą odpowiedzią (lub okropną odpowiedzią). Dokumentacja os.system () sama mówi „Użyj modułu podprocesu”. Czego więcej potrzeba? To mi wystarczy.
Devin Jeanpierre
20
Trochę niechętnie wznawiam tę dyskusję, ale myślę, że sekcja „Późniejsza aktualizacja” jest całkowicie błędna. Problem os.system()polega na tym, że używa powłoki (i nie wykonujesz tutaj żadnej ucieczki powłoki, więc złe rzeczy będą się zdarzać w przypadku doskonale poprawnych nazw plików, które zawierają meta-znaki powłoki). Powodem, dla którego subprocess.call()jest to preferowane jest to, że masz możliwość ominięcia powłoki za pomocą subprocess.call(["open", filename]). Działa to dla wszystkich prawidłowych nazw plików i nie wprowadza luki umożliwiającej wstrzyknięcie powłoki nawet w przypadku niezaufanych nazw plików.
Sven Marnach
151

Nie używaj subprocessmodułu dostępnego w Pythonie 2.4+, os.system()więc nie musisz zajmować się ucieczką z powłoki.

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

Podwójne nawiasy są spowodowane subprocess.call()tym, że jako pierwszy argument potrzebny jest ciąg, więc używamy tutaj krotki. W systemach Linux z Gnome istnieje również gnome-openpolecenie, które robi to samo, ale xdg-openjest standardem Free Desktop Foundation i działa w środowiskach Linux.

Nacięcie
źródło
5
Używanie 'start' w subprocess.call () nie działa w systemie Windows - start nie jest tak naprawdę plikiem wykonywalnym.
Tomas Sedovic
4
nitpick: na wszystkich linuksach (i chyba większości BSD) powinieneś używać xdg-open- linux.die.net/man/1/xdg-open
gnud
6
start w systemie Windows jest poleceniem powłoki, a nie plikiem wykonywalnym. Możesz użyć subprocess.call (('start', filepath), shell = True), chociaż jeśli wykonujesz w powłoce, równie dobrze możesz użyć os.system.
Peter Graham
Uruchomiłem xdg-open test.pyi otworzyło się dla mnie okno dialogowe pobierania programu Firefox. Co jest nie tak? Jestem na Linuksie Manjaro.
Jason
1
@Jason Wygląda na xdg-opento, że twoja konfiguracja jest zdezorientowana, ale nie jest to coś, co możemy rozwiązać w komentarzu. Może zobacz unix.stackexchange.com/questions/36380/ ...
tripleee
44

Wolę:

os.startfile(path, 'open')

Zauważ, że ten moduł obsługuje nazwy plików, które mają spacje w swoich folderach i plikach, np

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

( Python docs ) „open” nie musi być dodawane (jest to ustawienie domyślne). Dokumenty wyraźnie wspominają, że jest to podobne do dwukrotnego kliknięcia ikony pliku w Eksploratorze Windows.

To rozwiązanie dotyczy tylko okien.

DrBloodmoney
źródło
Dzięki. Nie zauważyłem dostępności, ponieważ dokumentacja ma to dołączone do ostatniego akapitu. W większości innych sekcji notatka o dostępności zajmuje osobną linię.
DrBloodmoney
W systemie Linux z jakiegoś powodu, zamiast wywoływać błąd, startfilefunkcja nawet nie istnieje, co oznacza, że ​​użytkownicy otrzymają mylący komunikat o błędzie dotyczący brakującej funkcji. Możesz sprawdzić platformę, aby tego uniknąć.
cz
39

Dla kompletności (nie było tego w pytaniu), xdg-open zrobi to samo na Linuksie.

dF.
źródło
6
+1 Zwykle respondenci nie powinni odpowiadać na pytania, które nie zostały zadane, ale w tym przypadku uważam, że jest to bardzo istotne i pomocne dla całej społeczności SO jako całości.
demongolem
szukał tego
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
źródło
2
Huh, nie wiedziałem o pliku startowym. Byłoby miło, gdyby wersje Pythona dla komputerów Mac i Linux przyjęły podobną semantykę.
Nick
3
Odpowiedni błąd w Pythonie: bugs.python.org/issue3177 - dostarcz fajną łatkę, która może zostać zaakceptowana =)
gnud
xdg-open command dla systemu Linux
TheTechRobo36414519
21

Jeśli musisz użyć metody heurystycznej, możesz rozważyć webbrowser.
Jest to standardowa biblioteka i pomimo swojej nazwy próbuje też otwierać pliki:

Zauważ, że na niektórych platformach próba otwarcia nazwy pliku przy użyciu tej funkcji może zadziałać i uruchomić powiązany program systemu operacyjnego. Jednak nie jest to obsługiwane ani przenośne. ( Odniesienie )

Wypróbowałem ten kod i działał dobrze w Windows 7 i Ubuntu Natty:

import webbrowser
webbrowser.open("path_to_file")

Ten kod działa również dobrze w systemie Windows XP Professional, przy użyciu przeglądarki Internet Explorer 8.

etuardu
źródło
3
O ile wiem, jest to zdecydowanie najlepsza odpowiedź. Wydaje się, że jest wieloplatformowy i nie ma potrzeby sprawdzania, która platforma jest używana, ani importowania systemu operacyjnego, platformy.
polandeer
2
@jonathanrocher: Widzę obsługę Maca w kodzie źródłowym . Używa open locationtam, co powinno działać, jeśli podasz ścieżkę jako prawidłowy adres URL.
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 "Zauważ, że na niektórych platformach próba otwarcia nazwy pliku przy użyciu [webbrowser.open (url)] może zadziałać i uruchomić powiązany program systemu operacyjnego. Jednak , to nie jest obsługiwane ani przenośne. ”
nyanpasu64
6

Jeśli chcesz iść subprocess.call()drogą, powinno to wyglądać tak w systemie Windows:

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

Nie możesz po prostu użyć:

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

ponieważ start nie jest plikiem wykonywalnym, ale poleceniem cmd.exeprogramu. To działa:

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

ale tylko wtedy, gdy w FILE_NAME nie ma spacji.

Chociaż subprocess.callmetoda en poprawnie cytuje parametry, startpolecenie ma dość dziwną składnię, gdzie:

start notes.txt

robi coś innego niż:

start "notes.txt"

Pierwszy cytowany ciąg powinien określać tytuł okna. Aby działało z przestrzeniami, musimy:

start "" "my notes.txt"

co robi kod na górze.

Tomas Sedovic
źródło
5

Start nie obsługuje długich nazw ścieżek i spacji. Musisz go przekonwertować na ścieżki kompatybilne z 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 )

Plik musi istnieć, aby działał z wywołaniem API.

bFloch
źródło
1
Innym start "Title" "C:\long path to\file.avi"
sposobem
3

Spóźniłem się na wiele, ale oto rozwiązanie wykorzystujące interfejs API systemu Windows. Spowoduje to zawsze otwarcie skojarzonej aplikacji.

import ctypes

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

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

Wiele magicznych stałych. Pierwsze zero to hwnd bieżącego programu. Może wynosić zero. Pozostałe dwa zera to parametry opcjonalne (parametry i katalog). 5 == SW_SHOW, określa, jak uruchomić aplikację. Przeczytaj dokumentację ShellExecute API, aby uzyskać więcej informacji.

Jerzy
źródło
1
jak to wypada os.startfile(file)?
jfs
2

w systemie Mac OS możesz nazwać „otwórz”

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

spowoduje to otwarcie pliku za pomocą TextEdit lub dowolnej aplikacji ustawionej jako domyślna dla tego typu pliku

lcvinny
źródło
2

Jeśli chcesz określić aplikację do otwierania pliku w systemie Mac OS X, użyj tego: os.system("open -a [app name] [file name]")


źródło
2

W systemie Windows 8.1 poniżej działały, podczas gdy inne podane sposoby z subprocess.callbłędami ze ścieżką mają spacje.

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

Korzystając wcześniej z tego i innych odpowiedzi, oto kod wbudowany, który działa na wielu platformach.

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
źródło
2

os.startfile(path, 'open')pod Windows jest dobre, ponieważ gdy istnieją spacje w katalogu, os.system('start', path_name)nie można poprawnie otworzyć aplikacji, a gdy i18n istnieje w katalogu, os.systemmusi zmienić unicode na kodek konsoli w systemie Windows.

BearPy
źródło