Documento abierto con la aplicación predeterminada del sistema operativo en Python, tanto en Windows como en Mac OS

126

Necesito poder abrir un documento usando su aplicación predeterminada en Windows y Mac OS. Básicamente, quiero hacer lo mismo que sucede cuando haces doble clic en el ícono del documento en Explorer o Finder. ¿Cuál es la mejor manera de hacer esto en Python?

Abdullah Jibaly
fuente
9
Ha habido un problema para que esto se incluya en la biblioteca estándar en el rastreador de Python desde 2008: bugs.python.org/issue3177
Ram Rachum

Respuestas:

77

openy startson cosas de intérprete de comandos para Mac OS / X y Windows respectivamente, para hacer esto.

Para llamarlos desde Python, puede usar subprocessmodule o os.system().

Aquí hay consideraciones sobre qué paquete usar:

  1. Puedes llamarlos a través de os.system, que funciona, pero ...

    Escapar: os.system solo funciona con nombres de archivo que no tienen espacios u otros metacaracteres de shell en el nombre de ruta (por ejemplo A:\abc\def\a.txt), o de lo contrario estos deben escaparse. Existe shlex.quotepara sistemas tipo Unix, pero nada realmente estándar para Windows. Quizás vea también python, windows: análisis de líneas de comando con shlex

    • Mac OS X: os.system("open " + shlex.quote(filename))
    • Windows: os.system("start " + filename)en el que también se filenamedebe escapar correctamente hablando .
  2. También puede llamarlos a través del subprocessmódulo, pero ...

    Para Python 2.7 y posteriores, simplemente use

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

    En Python 3.5+ puedes usar de manera equivalente el más complejo pero también algo más versátil.

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

    Si necesita ser compatible hasta Python 2.4, puede usar subprocess.call()e implementar su propia comprobación de errores:

    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

    Ahora, ¿cuáles son las ventajas de usar subprocess?

    • Seguridad: en teoría, esto es más seguro, pero de hecho necesitamos ejecutar una línea de comando de una forma u otra; En cualquier entorno, necesitamos el entorno y los servicios para interpretar, obtener caminos, etc. En ninguno de los casos estamos ejecutando texto arbitrario, por lo que no tiene un problema inherente "pero puede escribir 'filename ; rm -rf /'", y si el nombre del archivo puede estar dañado, el uso subprocess.callnos brinda poca protección adicional.
    • Manejo de errores: en realidad no nos da más detección de errores, todavía dependemos de los retcodecasos; pero el comportamiento de generar explícitamente una excepción en el caso de un error ciertamente lo ayudará a notar si hay una falla (aunque en algunos escenarios, un rastreo puede no ser más útil que simplemente ignorar el error).
    • Genera un subproceso (sin bloqueo) : no es necesario que esperemos el proceso secundario, ya que por enunciado del problema comenzamos un proceso separado.

    A la objeción "Pero subprocessse prefiere". Sin embargo, os.system()no está en desuso, y en cierto sentido es la herramienta más simple para este trabajo en particular. Conclusión: usandoos.system() es, por lo tanto, también una respuesta correcta.

    Una desventaja marcada es que el startcomando de Windows requiere que pase, lo shell=Trueque niega la mayoría de los beneficios del uso subprocess.

Charlie Martin
fuente
2
Dependiendo de dónde filenamevenga la forma, este es un ejemplo perfecto de por qué os.system () es inseguro y malo. El subproceso es mejor.
Devin Jeanpierre
66
La respuesta de Nick me pareció bien. Nada se interpuso en el camino. Explicar cosas usando ejemplos incorrectos no es fácilmente justificable.
Devin Jeanpierre
2
Es menos seguro y menos flexible que usar un subproceso. Eso me suena mal.
Devin Jeanpierre
8
Por supuesto que importa. Es la diferencia entre una buena respuesta y una mala respuesta (o una respuesta terrible). Los documentos para os.system () dicen "Usar el módulo de subproceso". ¿Qué más se necesita? Eso es suficiente desaprobación para mí.
Devin Jeanpierre
20
Me siento un poco reacio a reiniciar esta discusión, pero creo que la sección "Actualización posterior" se equivoca completamente. El problema os.system()es que usa el shell (y no está haciendo ningún escape de shell aquí, por lo que sucederán cosas malas para nombres de archivo perfectamente válidos que contengan metacaracteres de shell). La razón por la que subprocess.call()se prefiere es que tiene la opción de omitir el shell mediante el uso subprocess.call(["open", filename]). Esto funciona para todos los nombres de archivo válidos, y no introduce una vulnerabilidad de inyección de shell incluso para nombres de archivo no confiables.
Sven Marnach
151

Utilizar el subprocess módulo disponible en Python 2.4+, no os.system(), para que no tenga que lidiar con el escape de 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))

Los paréntesis dobles son porque subprocess.call()quiere una secuencia como primer argumento, así que estamos usando una tupla aquí. En los sistemas Linux con Gnome también hay un gnome-opencomando que hace lo mismo, pero xdg-openes el estándar Free Desktop Foundation y funciona en entornos de escritorio Linux.

Mella
fuente
55
El uso de 'inicio' en subprocess.call () no funciona en Windows: el inicio no es realmente un ejecutable.
Tomás Sedovic
44
nitpick: sobre todo linuxen (y supongo que la mayoría de BSD) se debe utilizar xdg-open- linux.die.net/man/1/xdg-open
gnud
66
Inicio en Windows es un comando de shell, no un ejecutable. Puede usar subprocess.call (('inicio', filepath), shell = True), aunque si está ejecutando en un shell también podría usar os.system.
Peter Graham
Corrí xdg-open test.pyy me abrió el diálogo de descarga de Firefox. Que pasa Estoy en Manjaro Linux.
Jason
1
@ Jason Parece que su xdg-openconfiguración es confusa, pero eso no es realmente algo que podamos solucionar en un comentario. Quizás vea unix.stackexchange.com/questions/36380/…
tripleee
44

Yo prefiero:

os.startfile(path, 'open')

Tenga en cuenta que este módulo admite nombres de archivo que tienen espacios en sus carpetas y archivos, por ejemplo

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

( documentos de Python ) 'abierto' no tiene que agregarse (es el valor predeterminado). Los documentos mencionan específicamente que esto es como hacer doble clic en el icono de un archivo en el Explorador de Windows.

Esta solución es solo para Windows.

DrBloodmoney
fuente
Gracias. No noté la disponibilidad, ya que los documentos lo han agregado al último párrafo. En la mayoría de las otras secciones, la nota de disponibilidad ocupa su propia línea.
DrBloodmoney
En Linux por alguna razón, en lugar de generar un error, la startfilefunción ni siquiera existe, lo que significa que los usuarios recibirán un mensaje de error confuso sobre una función faltante. Es posible que desee comprobar la plataforma para evitar esto.
cz
39

Solo para completar (no estaba en la pregunta), xdg-open hará lo mismo en Linux.

dF.
fuente
66
+1 Por lo general, los respondedores no deben responder preguntas que no se hicieron, pero en este caso creo que es muy relevante y útil para la comunidad SO en su conjunto.
demongolem
estaba buscando esto
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
fuente
2
Huh, no sabía sobre el archivo de inicio. Sería bueno si las versiones para Mac y Linux de Python recogieran una semántica similar.
Nick
3
Relevante fallo pitón: bugs.python.org/issue3177 - proporcionar un parche agradable, y quizás lo acepten =)
gnud
Comando xdg-open para Linux
TheTechRobo36414519
21

Si tiene que usar un método heurístico, puede considerarlo webbrowser.
Es una biblioteca estándar y, a pesar de su nombre, también intentaría abrir archivos:

Tenga en cuenta que, en algunas plataformas, intentar abrir un nombre de archivo con esta función puede funcionar e iniciar el programa asociado del sistema operativo. Sin embargo, esto no es compatible ni portátil. ( Referencia )

Probé este código y funcionó bien en Windows 7 y Ubuntu Natty:

import webbrowser
webbrowser.open("path_to_file")

Este código también funciona bien en Windows XP Professional, utilizando Internet Explorer 8.

etuardu
fuente
3
Por lo que puedo decir, esta es la mejor respuesta. Parece multiplataforma y no es necesario verificar qué plataforma está en uso o importar os, plataforma.
polandeer
2
@jonathanrocher: veo soporte para Mac en el código fuente . Utiliza open locationallí que debería funcionar si proporciona la ruta como una URL válida.
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 "Tenga en cuenta que en algunas plataformas, intentar abrir un nombre de archivo usando [webbrowser.open (url)], puede funcionar e iniciar el programa asociado del sistema operativo. Sin embargo , esto no es compatible ni portátil ".
nyanpasu64
6

Si desea seguir el subprocess.call()camino, debería verse así en Windows:

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

No puedes simplemente usar:

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

porque start no es un ejecutable sino un comando del cmd.exeprograma. Esto funciona:

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

pero solo si no hay espacios en FILE_NAME.

Si bien el subprocess.callmétodo en cita los parámetros correctamente, el startcomando tiene una sintaxis bastante extraña, donde:

start notes.txt

hace algo más que:

start "notes.txt"

La primera cadena entre comillas debe establecer el título de la ventana. Para que funcione con espacios, tenemos que hacer:

start "" "my notes.txt"

que es lo que hace el código de arriba.

Tomás Sedovic
fuente
5

Inicio no admite nombres de ruta largos y espacios en blanco. Tienes que convertirlo a 8.3 rutas compatibles.

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 )

El archivo tiene que existir para poder trabajar con la llamada API.

bFloch
fuente
1
Otra solución es darle un título entre comillas, por ejemplostart "Title" "C:\long path to\file.avi"
user3364825
3

Llego bastante tarde al lote, pero aquí hay una solución usando la API de Windows. Esto siempre abre la aplicación asociada.

import ctypes

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

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

Muchas constantes mágicas. El primer cero es el hwnd del programa actual. Puede ser cero. Los otros dos ceros son parámetros opcionales (parámetros y directorio). 5 == SW_SHOW, especifica cómo ejecutar la aplicación. Lea los documentos de la API ShellExecute para obtener más información.

Jorge
fuente
1
¿Cómo se compara con os.startfile(file)?
jfs
2

en mac os puedes llamar 'abierto'

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

esto abriría el archivo con TextEdit, o cualquier aplicación que esté configurada como predeterminada para este tipo de archivo

lcvinny
fuente
2

Si desea especificar la aplicación para abrir el archivo en Mac OS X, use esto: os.system("open -a [app name] [file name]")


fuente
2

En Windows 8.1, a continuación han funcionado, mientras que otras formas dadas con subprocess.callfallas con la ruta tienen espacios.

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

Al utilizar esta y otras respuestas anteriores, aquí hay un código en línea que funciona en múltiples plataformas.

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

os.startfile(path, 'open')en Windows es bueno porque cuando existen espacios en el directorio, os.system('start', path_name)no se puede abrir la aplicación correctamente y cuando el i18n existe en el directorio, os.systemnecesita cambiar el código Unicode al códec de la consola en Windows.

BearPy
fuente