Abrir documento com o aplicativo padrão do SO em Python, no Windows e no Mac OS

125

Preciso abrir um documento usando seu aplicativo padrão no Windows e Mac OS. Basicamente, quero fazer o mesmo que acontece quando você clica duas vezes no ícone do documento no Explorer ou no Finder. Qual é a melhor maneira de fazer isso no Python?

Abdullah Jibaly
fonte
9
Não tem sido um problema para que isso seja incluído na biblioteca padrão do rastreador Python a partir de 2008: bugs.python.org/issue3177
Ram Rachum

Respostas:

77

opene startsão coisas de interpretador de comandos para Mac OS / X e Windows, respectivamente, para fazer isso.

Para chamá-los do Python, você pode usar subprocessmodule ou os.system().

Aqui estão algumas considerações sobre qual pacote usar:

  1. Você pode chamá-los via os.system, o que funciona, mas ...

    Escapando: os.system funciona apenas com nomes de arquivos que não possuem espaços ou outros metacaracteres de shell no nome do caminho (por exemplo A:\abc\def\a.txt), ou eles precisam ser escapados. Existe shlex.quotepara sistemas tipo Unix, mas nada realmente padrão para o Windows. Talvez veja também python, windows: analisando linhas de comando com shlex

    • Mac OS X: os.system("open " + shlex.quote(filename))
    • Windows: os.system("start " + filename)onde se fala corretamente também filenamedeve ser evitado.
  2. Você também pode chamá-los via subprocessmódulo, mas ...

    Para Python 2.7 e mais recente, basta usar

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

    No Python 3.5+, você pode usar de forma equivalente o um pouco mais complexo, mas também um pouco mais versátil

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

    Se você precisar ser compatível desde o Python 2.4, poderá usar subprocess.call()e implementar sua própria verificação de erro:

    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

    Agora, quais são as vantagens de usar subprocess?

    • Segurança: em teoria, isso é mais seguro, mas na verdade precisamos executar uma linha de comando de uma maneira ou de outra; em qualquer um dos ambientes, precisamos do ambiente e dos serviços para interpretar, encontrar caminhos etc. Em nenhum dos casos, estamos executando texto arbitrário, para que ele não tenha um problema inerente "mas você pode digitar 'filename ; rm -rf /'" e, se o nome do arquivo puder ser corrompido, o uso de subprocess.calluma proteção adicional é reduzida.
    • Tratamento de erros: na verdade, não nos dá mais nenhuma detecção de erros, ainda estamos dependendo dos retcodedois casos; mas o comportamento de gerar uma exceção explicitamente no caso de um erro certamente o ajudará a perceber se houver uma falha (embora em alguns cenários, um retorno de retorno possa não ser mais útil do que simplesmente ignorar o erro).
    • Gera um subprocesso (sem bloqueio) : não precisamos esperar pelo processo filho, pois estamos com a declaração do problema iniciando um processo separado.

    À objeção "Mas subprocessé preferível". No entanto, os.system()não é preterido e, de certa forma, é a ferramenta mais simples para esse trabalho em particular. Conclusão: usar os.system()é, portanto, também uma resposta correta.

    Uma desvantagem acentuada é que o startcomando do Windows exige que você passe, o shell=Trueque nega a maioria dos benefícios do uso subprocess.

Charlie Martin
fonte
2
Dependendo de onde filenamevem a forma, este é um exemplo perfeito de por que o os.system () é inseguro e ruim. subprocesso é melhor.
28911 Devin Jeanpierre
6
A resposta de Nick pareceu bem para mim. Nada ficou no caminho. Explicar coisas usando exemplos errados não é facilmente justificável.
Devin Jeanpierre
2
É menos seguro e menos flexível do que o subprocesso. Isso parece errado para mim.
Devin Jeanpierre
8
Claro que isso importa. É a diferença entre uma boa resposta e uma resposta ruim (ou uma resposta terrível). Os documentos para os.system () dizem "Use o módulo de subprocesso". O que mais é necessário? Isso é depreciação o suficiente para mim.
Devin Jeanpierre
20
Sinto-me um pouco relutante em reiniciar esta discussão, mas acho que a seção "Atualização posterior" entendeu tudo errado. O problema os.system()é que ele usa o shell (e você não está escapando aqui, então Bad Things acontecerá para nomes de arquivos perfeitamente válidos que contenham meta-caracteres do shell). O motivo pelo qual subprocess.call()é preferido é que você tem a opção de ignorar o shell usando subprocess.call(["open", filename]). Isso funciona para todos os nomes de arquivos válidos e não introduz uma vulnerabilidade de injeção de shell, mesmo para nomes de arquivos não confiáveis.
Sven Marnach
150

Use o subprocessmódulo disponível no Python 2.4+, não os.system(), para que você não precise lidar com o 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))

Os parênteses duplos são porque subprocess.call()deseja uma sequência como seu primeiro argumento, então estamos usando uma tupla aqui. Nos sistemas Linux com Gnome, também existe um gnome-opencomando que faz a mesma coisa, mas xdg-opené o padrão Free Desktop Foundation e funciona em ambientes de desktop Linux.

usuario
fonte
5
O uso de 'start' no subprocess.call () não funciona no Windows - o start não é realmente um executável.
Tomas Sedovic 18/10/09
4
nitpick: em todos os sistemas Linux (e eu acho que a maioria dos BSDs) você deve usar xdg-open- linux.die.net/man/1/xdg-open
gnud
6
O start no Windows é um comando shell, não um executável. Você pode usar subprocess.call (('start', caminho do arquivo), shell = True), embora se estiver executando em um shell, também use o os.system.
Peter Graham
Eu corri xdg-open test.pye ele abriu a caixa de diálogo de download do firefox para mim. O que há de errado? Estou no manjaro linux.
Jason
1
@ Jason Parece que sua xdg-openconfiguração está confusa, mas isso não é algo que possamos solucionar em um comentário. Talvez veja unix.stackexchange.com/questions/36380/…
tripleee
44

Eu prefiro:

os.startfile(path, 'open')

Observe que este módulo suporta nomes de arquivos com espaços em suas pastas e arquivos, por exemplo

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

( docs python ) 'open' não precisa ser adicionado (é o padrão). Os documentos mencionam especificamente que isso é como clicar duas vezes no ícone de um arquivo no Windows Explorer.

Esta solução é apenas para Windows.

DrBloodmoney
fonte
Obrigado. Não notei a disponibilidade, pois os documentos foram anexados ao último parágrafo. Na maioria das outras seções, a nota de disponibilidade ocupa sua própria linha.
DrBloodmoney
No Linux, por algum motivo, em vez de gerar um erro, a startfilefunção nem existe, o que significa que os usuários receberão uma mensagem de erro confusa sobre uma função ausente. Você pode querer verificar a plataforma para evitar isso.
cz
39

Apenas para completar (não estava em questão), o xdg-open fará o mesmo no Linux.

dF.
fonte
6
+1 Normalmente, os respondentes não devem responder perguntas que não foram feitas, mas, neste caso, acho que é muito relevante e útil para a comunidade de SOs como um todo.
Demongolem
estava procurando por isso
quinta
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
fonte
2
Huh, eu não sabia sobre o arquivo de início. Seria bom se as versões para Python para Mac e Linux adotassem semânticas semelhantes.
Nick as
3
Relevante bug python: bugs.python.org/issue3177 - fornecer um patch bom, e que poderia ser aceito =)
gnud
Comando xdg-open para linux
TheTechRobo36414519
21

Se você precisar usar um método heurístico, considere webbrowser.
É uma biblioteca padrão e, apesar do nome, também tentaria abrir arquivos:

Observe que em algumas plataformas, tentar abrir um nome de arquivo usando esta função, pode funcionar e iniciar o programa associado ao sistema operacional. No entanto, isso não é suportado nem portátil. ( Referência )

Eu tentei esse código e funcionou bem no Windows 7 e no Ubuntu Natty:

import webbrowser
webbrowser.open("path_to_file")

Esse código também funciona bem no Windows XP Professional, usando o Internet Explorer 8.

etuardu
fonte
3
Tanto quanto posso dizer, esta é de longe a melhor resposta. Parece multiplataforma e não é necessário verificar qual plataforma está em uso ou importar a plataforma.
polandeer
2
@ Jonathanrocher: Eu vejo o suporte ao Mac no código fonte . Ele usa open locationlá que deve funcionar se você der o caminho como um URL válido.
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 "Observe que em algumas plataformas, tentar abrir um nome de arquivo usando [webbrowser.open (url)], pode funcionar e iniciar o programa associado ao sistema operacional. , isso não é suportado nem portátil ".
nyanpasu64
6

Se você quiser seguir o subprocess.call()caminho, deve ficar assim no Windows:

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

Você não pode simplesmente usar:

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

porque start não é um executável, mas um comando do cmd.exeprograma. Isso funciona:

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

mas somente se não houver espaços no FILE_NAME.

Enquanto o subprocess.callmétodo en cita os parâmetros corretamente, o startcomando tem uma sintaxe bastante estranha, onde:

start notes.txt

faz algo diferente de:

start "notes.txt"

A primeira string entre aspas deve definir o título da janela. Para fazê-lo funcionar com espaços, precisamos fazer:

start "" "my notes.txt"

que é o que o código na parte superior faz.

Tomas Sedovic
fonte
5

O Start não suporta nomes de caminhos longos e espaços em branco. Você precisa convertê-lo em caminhos compatíveis com 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 )

O arquivo precisa existir para funcionar com a chamada da API.

bFloch
fonte
1
Outra solução alternativa é atribuir-lhe um título entre aspas, por exemplostart "Title" "C:\long path to\file.avi"
user3364825 21/21/14
3

Estou muito atrasado para o lote, mas aqui está uma solução usando a API do Windows. Isso sempre abre o aplicativo associado.

import ctypes

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

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

Muitas constantes mágicas. O primeiro zero é o hwnd do programa atual. Pode ser zero. Os outros dois zeros são parâmetros opcionais (parâmetros e diretório). 5 == SW_SHOW, especifica como executar o aplicativo. Leia os documentos da API ShellExecute para obter mais informações.

George
fonte
1
como ele se compara os.startfile(file)?
Jfs #
2

no mac os você pode chamar de 'aberto'

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

isso abriria o arquivo com o TextEdit ou qualquer aplicativo definido como padrão para esse tipo de arquivo

lcvinny
fonte
2

Se você deseja especificar o aplicativo para abrir o arquivo no Mac OS X, use este: os.system("open -a [app name] [file name]")


fonte
2

No Windows 8.1, abaixo funcionou, enquanto outras formas dadas com subprocess.callfalha no caminho possuem espaços.

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

Utilizando essas e outras respostas anteriores, aqui está um código embutido que funciona em várias 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
fonte
2

os.startfile(path, 'open')no Windows é bom porque, quando existem espaços no diretório, os.system('start', path_name)não é possível abrir o aplicativo corretamente e quando o i18n existe no diretório, é os.systemnecessário alterar o unicode para o codec do console no Windows.

BearPy
fonte