Відкрийте документ із програмою OS за замовчуванням у Python, як в Windows, так і в Mac OS


126

Мені потрібно мати можливість відкрити документ за допомогою програми за замовчуванням у Windows та Mac OS. В основному, я хочу зробити те саме, що відбувається, коли ви двічі клацніть піктограму документа в Провіднику чи Finder. Який найкращий спосіб зробити це в Python?


9
Проблема була включена до стандартної бібліотеки в трекер Python 2008 року: bugs.python.org/issue3177
Ram Rachum

Відповіді:


77

openі для цього startпотрібні речі інтерпретатора команд для Mac OS / X та Windows відповідно.

Щоб зателефонувати їм з Python, ви можете використовувати subprocessмодуль або os.system().

Ось міркування щодо того, який пакет використовувати:

  1. Ви можете зателефонувати їм через os.system, що працює, але ...

    Escaping: os.system працює лише з іменами файлів, які не мають пробілів або інших метахарактерів оболонки в імені шляху (наприклад A:\abc\def\a.txt), інакше їх потрібно уникнути. Існує shlex.quoteдля Unix-подібних систем, але насправді нічого не є стандартним для Windows. Можливо, дивіться також python, windows: розбір командних рядків shlex

    • MacOS / X: os.system("open " + shlex.quote(filename))
    • Windows: os.system("start " + filename)там, де правильно filenameслід говорити, слід також уникати.
  2. Ви також можете зателефонувати їм через subprocessмодуль, але ...

    Для Python 2.7 і новіших просто використовуйте

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

    У Python 3.5+ ви можете рівномірно використовувати трохи складніший, але також дещо більш універсальний

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

    Якщо вам потрібно бути сумісним на всьому шляху до Python 2.4, ви можете використовувати subprocess.call()та впроваджувати власну перевірку помилок:

    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

    Тепер, які переваги використання subprocess?

    • Безпека: Теоретично це більш безпечно, але насправді нам потрібно виконувати командний рядок так чи інакше; в будь-якому середовищі нам потрібне середовище та служби для інтерпретації, отримання шляхів тощо. У жодному випадку ми не виконуємо довільний текст, тому він не має вродженої проблеми "але ви можете ввести 'filename ; rm -rf /'" проблему, і якщо ім'я файлу може бути пошкоджено, використовуючиsubprocess.call дає нам небагато додаткового захисту.
    • Поводження з помилками : насправді це не дає нам більше виявлення помилок, ми все ще залежно від того і retcodeв іншому випадку; але поведінка явно створювати виняток у випадку помилки, безумовно, допоможе вам помітити, якщо стався збій (хоча в деяких сценаріях прослідкування може не бути кориснішим, ніж просто ігнорувати помилку).
    • Породжує (не блокуючий) підпроцес : нам не потрібно чекати дочірнього процесу, оскільки ми за допомогою постановки проблеми починаємо окремий процес.

    На заперечення "Але subprocessпереважніше". Однак os.system()це не застаріло, і це в деякому сенсі найпростіший інструмент для цієї конкретної роботи. Висновок: використання os.system(), отже, також є правильною відповіддю.

    Помітний недолік полягає в тому, що startкоманда Windows вимагає пройти, в shell=Trueякому заперечується більшість переваг використання subprocess.


2
Залежно від filenameформи, це ідеальний приклад того, чому os.system () є небезпечним і поганим. підпроцес краще.
Девін Жанп'єр

6
Відповідь Ніка мені добре виглядала. Ніщо не заважало. Пояснення речей на неправильних прикладах не є легко виправданим.
Девін Жанп'єр

2
Він менш безпечний і менш гнучкий, ніж використання підпроцесу. Це звучить для мене неправильно.
Девін Жанп'єр

8
Звичайно, це має значення. Це різниця між хорошою відповіддю і поганою (або жахливою). Документи для os.system () самі кажуть "Використовувати модуль підпроцесу." Що ще потрібно? Мені цього недооцінення достатньо.
Девін Жанп'єр

20
Я трохи не бажаю перезавантажувати цю дискусію, але думаю, що в розділі "Пізніше оновлення" це зовсім неправильно. Проблема os.system()полягає в тому, що він використовує оболонку (і ви не робите жодної оболонки, не втікаючи тут, тому погані речі трапляться для ідеально дійсних імен файлів, які містять мета-символи оболонки). Причина, чому subprocess.call()кращим є те, що у вас є можливість обійти оболонку за допомогою subprocess.call(["open", filename]). Це працює для всіх дійсних імен і не вводить вразливість оболонки для введення оболонки навіть для ненадійних імен файлів.
Свен Марнах

151

Використовуйте subprocessмодуль, доступний на Python 2.4+, ні os.system(), тож вам не доведеться мати справу із втечею оболонки.

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

Подвійні дужки є тому, що subprocess.call()потрібна послідовність як перший аргумент, тому ми тут використовуємо кортеж. У системах Linux з Gnome також є gnome-openкоманда, яка робить те ж саме, але xdg-openє стандартом Free Desktop Foundation і працює в різних робочих середовищах Linux.


5
Використання 'start' у subprocess.call () не працює в Windows - запуск насправді не виконується.
Томаш Седович

4
nitpick: на всіх linuxen (і, мабуть, більшість BSD), які ви повинні використовувати xdg-open- linux.die.net/man/1/xdg-open
gnud

6
start в Windows - це командна оболонка, а не виконується. Ви можете використовувати subprocess.call (('start', filepath), shell = True), хоча якщо ви виконуєте в оболонці, ви також можете використовувати os.system.
Пітер Грем

Я побіг, xdg-open test.pyі він відкрив для мене діалогове вікно завантаження Firefox. Що не так? Я на манджаро linux.
Джейсон

1
@Jason Здається, що ваша xdg-openконфігурація заплутана, але насправді це не те, що ми можемо вирішити в коментарі. Можливо, дивіться unix.stackexchange.com/questions/36380/…
tripleee

44

Я віддаю перевагу:

os.startfile(path, 'open')

Зауважте, що цей модуль підтримує назви файлів, які мають пробіли у своїх папках та файлах, наприклад

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

( python docs ) 'open' не потрібно додавати (це за замовчуванням). Документи спеціально зазначають, що це як подвійне клацання піктограми файлу в Провіднику Windows.

Це рішення лише для Windows.


Дякую. Я не помітив доступність, оскільки документи додали її до останнього абзацу. У більшості інших розділів примітка про доступність займає свій власний рядок.
DrBloodmoney

У Linux чомусь, замість того, щоб викликати помилку, startfileфункція навіть не існує, а це означає, що користувачі отримають заплутане повідомлення про помилку про відсутню функцію. Ви можете перевірити платформу, щоб уникнути цього.
cz

39

Тільки для повноти (про це не йшлося), xdg-open зробить те саме в Linux.


6
+1 Зазвичай респонденти не повинні відповідати на запитання, які не задавались, але в цьому випадку я вважаю, що це дуже актуально і корисно для спільноти ЗП в цілому.
демонголем

шукав це
Нуреттін

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

2
Так, я не знав про стартовий файл. Було б непогано, якби версія Mac і Linux Python підібрала подібну семантику.
Нік

3
Відповідна помилка python: bugs.python.org/issue3177 - надайте хороший патч, і він може бути прийнятий =)
gnud

Команда xdg-open для linux
TheTechRobo36414519

21

Якщо вам доведеться використовувати евристичний метод, ви можете розглянути webbrowser.
Це стандартна бібліотека, і незважаючи на свою назву, вона також намагатиметься відкривати файли:

Зауважте, що на деяких платформах спроба відкрити ім'я файлу за допомогою цієї функції може працювати і запускати пов'язану з цим програмою операційну систему. Однак це не підтримується і не є портативним. ( Довідка )

Я спробував цей код, і він відмінно працював у Windows 7 та Ubuntu Natty:

import webbrowser
webbrowser.open("path_to_file")

Цей код також добре працює в Windows XP Professional, використовуючи Internet Explorer 8.


3
Наскільки я можу сказати, це, безумовно, найкраща відповідь. Здається, кросплатформна і не потрібно перевіряти, яка платформа використовується чи імпортується ОС, платформа.
polandeer

2
@jonathanrocher: Я бачу підтримку Mac у вихідному коді . Він використовує open locationтам, що має працювати, якщо ви надаєте шлях як дійсний URL.
jfs

1
macOS:import webbrowser webbrowser.open("file:///Users/nameGoesHere/Desktop/folder/file.py")
Даніель Спрінгер

3
docs.python.org/3/library/webbrowser.html#webbrowser.open "Зауважте, що на деяких платформах, намагаючись відкрити ім'я файлу за допомогою [webbrowser.open (url)], може працювати і запускати пов'язану програму операційної системи. Однак , це ні підтримується, ні переноситься. "
nyanpasu64

6

Якщо ви хочете піти subprocess.call()шляхом, це має виглядати так у Windows:

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

Ви не можете просто використовувати:

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

тому що start це не виконуваний файл, а команда cmd.exeпрограми. Це працює:

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

але лише якщо у FILE_NAME немає пробілів.

Хоча subprocess.callметод en цитує параметри правильно, startкоманда має досить дивний синтаксис, де:

start notes.txt

робить щось інше, ніж:

start "notes.txt"

Перший рядок, що цитується, повинен встановлювати заголовок вікна. Щоб це працювало з просторами, ми повинні:

start "" "my notes.txt"

що робить код зверху.


5

Пуск не підтримує довгих імен шляху та пробілів. Ви повинні перетворити його на 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 )

Файл повинен існувати для роботи з викликом API.


1
Іншим вирішенням є надання йому заголовку в лапках, наприкладstart "Title" "C:\long path to\file.avi"
user3364825

3

Я досить запізнююсь на лот, але ось рішення з використанням Windows api. Це завжди відкриває пов'язаний додаток.

import ctypes

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

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

Багато магічних констант. Перший нуль - hwnd поточної програми. Може дорівнювати нулю. Інші дві нулі - необов'язкові параметри (параметри та каталог). 5 == SW_SHOW, він визначає, як виконати додаток. Прочитайте документи ShellExecute API для отримання додаткової інформації.


1
як це порівнюється os.startfile(file)?
jfs

2

на mac os ви можете зателефонувати "відкрити"

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

це відкриє файл за допомогою TextEdit або будь-якої іншої програми, встановленої для цього файлу за замовчуванням


2

Якщо ви хочете вказати програму для відкриття файлу в Mac OS X, скористайтеся цим: os.system("open -a [app name] [file name]")


2

У Windows 8.1, нижче, працювали, тоді як інші задані способи з subprocess.callпомилками з шляху мають в ньому пробіли.

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

Використовуючи цю та інші відповіді раніше, ось вбудований код, який працює на декількох платформах.

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

2

os.startfile(path, 'open')під Windows добре, тому що, коли в каталозі існують пробіли, os.system('start', path_name)не вдається відкрити програму належним чином і коли i18n існує в каталозі, os.systemпотрібно змінити unicode на кодек консолі Windows.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.