Open document with default OS application in Python, both in Windows and Mac OS
Question:
I need to be able to open a document using its default application in Windows and Mac OS. Basically, I want to do the same thing that happens when you double-click on the document icon in Explorer or Finder. What is the best way to do this in Python?
Answers:
open
and start
are command-interpreter things for Mac OS/X and Windows respectively, to do this.
To call them from Python, you can either use subprocess
module or os.system()
.
Here are considerations on which package to use:
-
You can call them via os.system
, which works, but…
Escaping: os.system
only works with filenames that don’t have any spaces or other shell metacharacters in the pathname (e.g. A:abcdefa.txt
), or else these need to be escaped. There is shlex.quote
for Unix-like systems, but nothing really standard for Windows. Maybe see also python, windows : parsing command lines with shlex
- MacOS/X:
os.system("open " + shlex.quote(filename))
- Windows:
os.system("start " + filename)
where properly speaking filename
should be escaped, too.
-
You can also call them via subprocess
module, but…
For Python 2.7 and newer, simply use
subprocess.check_call(['open', filename])
In Python 3.5+ you can equivalently use the slightly more complex but also somewhat more versatile
subprocess.run(['open', filename], check=True)
If you need to be compatible all the way back to Python 2.4, you can use subprocess.call()
and implement your own error checking:
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
Now, what are the advantages of using subprocess
?
- Security: In theory, this is more secure, but in fact we’re needing to execute a command line one way or the other; in either environment, we need the environment and services to interpret, get paths, and so forth. In neither case are we executing arbitrary text, so it doesn’t have an inherent “but you can type
'filename ; rm -rf /'
” problem, and if the file name can be corrupted, using subprocess.call
gives us little additional protection.
- Error handling: It doesn’t actually give us any more error detection, we’re still depending on the
retcode
in either case; but the behavior to explicitly raise an exception in the case of an error will certainly help you notice if there is a failure (though in some scenarios, a traceback might not at all be more helpful than simply ignoring the error).
- Spawns a (non-blocking) subprocess: We don’t need to wait for the child process, since we’re by problem statement starting a separate process.
To the objection “But subprocess
is preferred.” However, os.system()
is not deprecated, and it’s in some sense the simplest tool for this particular job. Conclusion: using os.system()
is therefore also a correct answer.
A marked disadvantage is that the Windows start
command requires you to pass in shell=True
which negates most of the benefits of using subprocess
.
On mac os you can call open
:
import os
os.open("open myfile.txt")
This would open the file with TextEdit, or whatever app is set as default for this filetype.
I prefer:
os.startfile(path, 'open')
Note that this module supports filenames that have spaces in their folders and files e.g.
A:abcfolder with spacesfile with-spaces.txt
(python docs) ‘open’ does not have to be added (it is the default). The docs specifically mention that this is like double-clicking on a file’s icon in Windows Explorer.
This solution is windows only.
Just for completeness (it wasn’t in the question), xdg-open will do the same on Linux.
Use the subprocess
module available on Python 2.4+, not os.system()
, so you don’t have to deal with shell escaping.
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))
The double parentheses are because subprocess.call()
wants a sequence as its first argument, so we’re using a tuple here. On Linux systems with Gnome there is also a gnome-open
command that does the same thing, but xdg-open
is the Free Desktop Foundation standard and works across Linux desktop environments.
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])
If you want to specify the app to open the file with on Mac OS X, use this:
os.system("open -a [app name] [file name]")
If you want to go the subprocess.call()
way, it should look like this on Windows:
import subprocess
subprocess.call(('cmd', '/C', 'start', '', FILE_NAME))
You can’t just use:
subprocess.call(('start', FILE_NAME))
because start
is not an executable but a command of the cmd.exe
program. This works:
subprocess.call(('cmd', '/C', 'start', FILE_NAME))
but only if there are no spaces in the FILE_NAME.
While subprocess.call
method enquotes the parameters properly, the start
command has a rather strange syntax, where:
start notes.txt
does something else than:
start "notes.txt"
The first quoted string should set the title of the window. To make it work with spaces, we have to do:
start "" "my notes.txt"
which is what the code on top does.
Start does not support long path names and white spaces. You have to convert it to 8.3 compatible paths.
import subprocess
import win32api
filename = "C:\Documents and Settings\user\Desktopfile.avi"
filename_short = win32api.GetShortPathName(filename)
subprocess.Popen('start ' + filename_short, shell=True )
The file has to exist in order to work with the API call.
I am pretty late to the lot, but here is a solution using the windows api. This always opens the associated application.
import ctypes
shell32 = ctypes.windll.shell32
file = 'somedocument.doc'
shell32.ShellExecuteA(0,"open",file,0,0,5)
A lot of magic constants. The first zero is the hwnd of the current program. Can be zero. The other two zeros are optional parameters (parameters and directory). 5 == SW_SHOW, it specifies how to execute the app.
Read the
ShellExecute API docs for more info.
If you have to use an heuristic method, you may consider webbrowser
.
It’s standard library and despite of its name it would also try to open files:
Note that on some platforms, trying to open a filename using this
function, may work and start the operating system’s associated
program. However, this is neither supported nor portable.
(Reference)
I tried this code and it worked fine in Windows 7 and Ubuntu Natty:
import webbrowser
webbrowser.open("path_to_file")
This code also works fine in Windows XP Professional, using Internet Explorer 8.
os.startfile(path, 'open')
under Windows is good because when spaces exist in the directory, os.system('start', path_name)
can’t open the app correctly and when the i18n exist in the directory, os.system
needs to change the unicode to the codec of the console in Windows.
On windows 8.1, below have worked while other given ways with subprocess.call
fails with path has spaces in it.
subprocess.call('cmd /c start "" "any file path with spaces"')
By utilizing this and other’s answers before, here’s an inline code which works on multiple platforms.
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))
Here is the answer from Nick, adjusted slightly for WSL:
import os
import sys
import logging
import subprocess
def get_platform():
if sys.platform == 'linux':
try:
proc_version = open('/proc/version').read()
if 'Microsoft' in proc_version:
return 'wsl'
except:
pass
return sys.platform
def open_with_default_app(filename):
platform = get_platform()
if platform == 'darwin':
subprocess.call(('open', filename))
elif platform in ['win64', 'win32']:
os.startfile(filename.replace('/','\'))
elif platform == 'wsl':
subprocess.call('cmd.exe /C start'.split() + [filename])
else: # linux variants
subprocess.call(('xdg-open', filename))
I think you might want to open file in editor.
For Windows
subprocess.Popen(["notepad", filename])
For Linux
subprocess.Popen(["text-editor", filename])
I built a small library combining the best options for cross-platform support:
$ pip install universal-startfile
then
from startfile import startfile
startfile("~/Downloads/example.png")
startfile("http://example.com")
I need to be able to open a document using its default application in Windows and Mac OS. Basically, I want to do the same thing that happens when you double-click on the document icon in Explorer or Finder. What is the best way to do this in Python?
open
and start
are command-interpreter things for Mac OS/X and Windows respectively, to do this.
To call them from Python, you can either use subprocess
module or os.system()
.
Here are considerations on which package to use:
-
You can call them via
os.system
, which works, but…Escaping:
os.system
only works with filenames that don’t have any spaces or other shell metacharacters in the pathname (e.g.A:abcdefa.txt
), or else these need to be escaped. There isshlex.quote
for Unix-like systems, but nothing really standard for Windows. Maybe see also python, windows : parsing command lines with shlex- MacOS/X:
os.system("open " + shlex.quote(filename))
- Windows:
os.system("start " + filename)
where properly speakingfilename
should be escaped, too.
- MacOS/X:
-
You can also call them via
subprocess
module, but…For Python 2.7 and newer, simply use
subprocess.check_call(['open', filename])
In Python 3.5+ you can equivalently use the slightly more complex but also somewhat more versatile
subprocess.run(['open', filename], check=True)
If you need to be compatible all the way back to Python 2.4, you can use
subprocess.call()
and implement your own error checking: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
Now, what are the advantages of using
subprocess
?- Security: In theory, this is more secure, but in fact we’re needing to execute a command line one way or the other; in either environment, we need the environment and services to interpret, get paths, and so forth. In neither case are we executing arbitrary text, so it doesn’t have an inherent “but you can type
'filename ; rm -rf /'
” problem, and if the file name can be corrupted, usingsubprocess.call
gives us little additional protection. - Error handling: It doesn’t actually give us any more error detection, we’re still depending on the
retcode
in either case; but the behavior to explicitly raise an exception in the case of an error will certainly help you notice if there is a failure (though in some scenarios, a traceback might not at all be more helpful than simply ignoring the error). - Spawns a (non-blocking) subprocess: We don’t need to wait for the child process, since we’re by problem statement starting a separate process.
To the objection “But
subprocess
is preferred.” However,os.system()
is not deprecated, and it’s in some sense the simplest tool for this particular job. Conclusion: usingos.system()
is therefore also a correct answer.A marked disadvantage is that the Windows
start
command requires you to pass inshell=True
which negates most of the benefits of usingsubprocess
. - Security: In theory, this is more secure, but in fact we’re needing to execute a command line one way or the other; in either environment, we need the environment and services to interpret, get paths, and so forth. In neither case are we executing arbitrary text, so it doesn’t have an inherent “but you can type
On mac os you can call open
:
import os
os.open("open myfile.txt")
This would open the file with TextEdit, or whatever app is set as default for this filetype.
I prefer:
os.startfile(path, 'open')
Note that this module supports filenames that have spaces in their folders and files e.g.
A:abcfolder with spacesfile with-spaces.txt
(python docs) ‘open’ does not have to be added (it is the default). The docs specifically mention that this is like double-clicking on a file’s icon in Windows Explorer.
This solution is windows only.
Just for completeness (it wasn’t in the question), xdg-open will do the same on Linux.
Use the subprocess
module available on Python 2.4+, not os.system()
, so you don’t have to deal with shell escaping.
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))
The double parentheses are because subprocess.call()
wants a sequence as its first argument, so we’re using a tuple here. On Linux systems with Gnome there is also a gnome-open
command that does the same thing, but xdg-open
is the Free Desktop Foundation standard and works across Linux desktop environments.
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])
If you want to specify the app to open the file with on Mac OS X, use this:
os.system("open -a [app name] [file name]")
If you want to go the subprocess.call()
way, it should look like this on Windows:
import subprocess
subprocess.call(('cmd', '/C', 'start', '', FILE_NAME))
You can’t just use:
subprocess.call(('start', FILE_NAME))
because start
is not an executable but a command of the cmd.exe
program. This works:
subprocess.call(('cmd', '/C', 'start', FILE_NAME))
but only if there are no spaces in the FILE_NAME.
While subprocess.call
method enquotes the parameters properly, the start
command has a rather strange syntax, where:
start notes.txt
does something else than:
start "notes.txt"
The first quoted string should set the title of the window. To make it work with spaces, we have to do:
start "" "my notes.txt"
which is what the code on top does.
Start does not support long path names and white spaces. You have to convert it to 8.3 compatible paths.
import subprocess
import win32api
filename = "C:\Documents and Settings\user\Desktopfile.avi"
filename_short = win32api.GetShortPathName(filename)
subprocess.Popen('start ' + filename_short, shell=True )
The file has to exist in order to work with the API call.
I am pretty late to the lot, but here is a solution using the windows api. This always opens the associated application.
import ctypes
shell32 = ctypes.windll.shell32
file = 'somedocument.doc'
shell32.ShellExecuteA(0,"open",file,0,0,5)
A lot of magic constants. The first zero is the hwnd of the current program. Can be zero. The other two zeros are optional parameters (parameters and directory). 5 == SW_SHOW, it specifies how to execute the app.
Read the
ShellExecute API docs for more info.
If you have to use an heuristic method, you may consider webbrowser
.
It’s standard library and despite of its name it would also try to open files:
Note that on some platforms, trying to open a filename using this
function, may work and start the operating system’s associated
program. However, this is neither supported nor portable.
(Reference)
I tried this code and it worked fine in Windows 7 and Ubuntu Natty:
import webbrowser
webbrowser.open("path_to_file")
This code also works fine in Windows XP Professional, using Internet Explorer 8.
os.startfile(path, 'open')
under Windows is good because when spaces exist in the directory, os.system('start', path_name)
can’t open the app correctly and when the i18n exist in the directory, os.system
needs to change the unicode to the codec of the console in Windows.
On windows 8.1, below have worked while other given ways with subprocess.call
fails with path has spaces in it.
subprocess.call('cmd /c start "" "any file path with spaces"')
By utilizing this and other’s answers before, here’s an inline code which works on multiple platforms.
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))
Here is the answer from Nick, adjusted slightly for WSL:
import os
import sys
import logging
import subprocess
def get_platform():
if sys.platform == 'linux':
try:
proc_version = open('/proc/version').read()
if 'Microsoft' in proc_version:
return 'wsl'
except:
pass
return sys.platform
def open_with_default_app(filename):
platform = get_platform()
if platform == 'darwin':
subprocess.call(('open', filename))
elif platform in ['win64', 'win32']:
os.startfile(filename.replace('/','\'))
elif platform == 'wsl':
subprocess.call('cmd.exe /C start'.split() + [filename])
else: # linux variants
subprocess.call(('xdg-open', filename))
I think you might want to open file in editor.
For Windows
subprocess.Popen(["notepad", filename])
For Linux
subprocess.Popen(["text-editor", filename])
I built a small library combining the best options for cross-platform support:
$ pip install universal-startfile
then
from startfile import startfile
startfile("~/Downloads/example.png")
startfile("http://example.com")