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?
-
12There's been an issue for this to be included in the standard library in the Python tracker from 2008: http://bugs.python.org/issue3177 – Ram Rachum Jul 09 '11 at 12:17
17 Answers
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.

- 33,645
- 23
- 135
- 217

- 21,555
- 18
- 47
- 50
-
6Using 'start' in subprocess.call() doesn't work on Windows -- start is not really an executable. – Tomas Sedovic Oct 18 '09 at 19:10
-
4nitpick: on all linuxen (and I guess most BSDs) you should use `xdg-open` - http://linux.die.net/man/1/xdg-open – gnud Oct 18 '09 at 19:57
-
8start on Windows is a shell command, not an executable. You can use subprocess.call(('start', filepath), shell=True), although if you're executing in a shell you might as well use os.system. – Peter Graham Feb 03 '11 at 23:25
-
I ran `xdg-open test.py` and it opened firefox download dialog for me. What's wrong? I'm on manjaro linux. – Jason Sep 12 '18 at 03:28
-
1@Jason Sounds like your `xdg-open` configuration is confused, but that's not really something we can troubleshoot in a comment. Maybe see https://unix.stackexchange.com/questions/36380/how-to-properly-and-easy-configure-xdg-open-without-any-environment – tripleee May 08 '19 at 06:20
-
Nitpick: `subprocess.call()` runs the command, but doesn't check its exit code. You should probably prefer `subprocess.check_call()` or its modern-day equivalent `subprocess.run(..., check=True)` – tripleee May 08 '19 at 06:22
-
@tripleee thanks for the info. Mine was in `openbox` too. I've moved to cinnamon and `xdg-open` works correctly now. – Jason May 08 '19 at 13:14
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:\abc\def\a.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

- 175,061
- 34
- 275
- 318

- 110,348
- 25
- 193
- 263
-
3Depending where `filename` comes form, this is a perfect example of why os.system() is insecure and bad. subprocess is better. – Devin Jeanpierre Mar 28 '09 at 16:36
-
1not to mention the wonders that could be done by changing the default binding for the filetype. Get a grip: this was to show in a simple example how open and start would be used. details of Popen.subprocess would just be in the way. – Charlie Martin Mar 28 '09 at 18:57
-
7Nick's answer looked fine to me. Nothing got in the way. Explaining things using wrong examples isn't easily justifiable. – Devin Jeanpierre Mar 29 '09 at 17:10
-
2It's less secure and less flexible than using subprocess. That sounds wrong to me. – Devin Jeanpierre Mar 29 '09 at 18:35
-
2Security issues of this sort affect not only the original programmer, not just the people who use the program, but potentially everybody on the internet. You need a damn good reason to justify such an educational shortcut, and since this one is trivial to do right... there's no excuse. – Rhamphoryncus Mar 30 '09 at 05:38
-
1and it still doesn't matter a bit to answering the original question. Nor, since open/start have MASSIVE security issues of their own, does it make a lot of difference. If you want to deprecate os.system, take it to Guido. – Charlie Martin Mar 30 '09 at 17:38
-
8Of course it matters. It's the difference between a good answer and a bad answer (or a terrible answer). The docs for os.system() themselves say "Use the subprocess module." What more is needed? That's deprecation enough for me. – Devin Jeanpierre Mar 30 '09 at 19:46
-
os.system() is a mapping of the C API. There's a lot of existing code built on it (not all of which is vulnerable.) Marking it deprecated wouldn't change anything, as you'd simply ignore what you're told and continue advocating it. – Rhamphoryncus Mar 31 '09 at 02:35
-
1
-
20I feel a bit reluctant to restart this discussion, but I think the "Later update" section gets it entirely wrong. The problem with `os.system()` is that it uses the shell (and you are not doing any shell escaping here, so Bad Things will happen for perfectly valid filenames that happen to contain shell meta-characters). The reason why `subprocess.call()` is preferred is that you have the option to bypass the shell by using `subprocess.call(["open", filename])`. This works for all valid filenames, and doesn't introduce a shell-injection vulnerability even for untrusted filenames. – Sven Marnach Apr 23 '12 at 14:55
-
Sven, particularly in the case of `start`, that's a built-in in CMD.exe. How do you propose to avoid the shell? You might have a point with `open` since I beelive it more or less directly goes to `launchd`. – Charlie Martin Nov 04 '12 at 01:07
-
I like the ´os.system("start "+filename)´ solution since I'm only going to use it on windows. But how do you close that process? – Norfeldt Jul 16 '13 at 07:41
-
Once you do start, the new process is separate; it runs to completion however it completes. Your call to `os.system()` returns (almost) immediately. If you need to control the subprocess, you should use os.subprocess. – Charlie Martin Jul 17 '13 at 13:08
-
2I had problems with weird characters in filenames (namely ![], and space), so I had to do `subprocess.check_call(('start', '', filename), shell=True)` The first argument to start is apparently the title. (check_call will raise an error if return code is not 0, I prefer it in most situations to `subprocess.call`) (nm apparently `os.startfile` is a thing) – Mark Harviston May 09 '14 at 07:55
-
Quick thing to mention here: the `subprocess` is only for python 2.7. In Python 3, you would have to change the file with the chevrons to the `file` argument in your `print` function. – Zizouz212 Jun 01 '15 at 14:44
-
2It might be worth adding that on Windows, if the filename contains spaces, you can still use `os.system`: `os.system('start "" "' + filename + '"')` (The `""` is necessary so that Windows does not interpret the file name as the title of the new window.) – schtandard Feb 07 '17 at 11:53
-
Adding quotes only works if you know that `filename` doesn't itself contain quotes or other metacharacters. The proper solution is to use a dedicated quoting function; but using `subprocess` without `shell=True` lets you completely bypass this complication. – tripleee May 08 '19 at 06:13
-
@Zizouz212 `subprocess` was introduced in Python 2.4, though e.g. the `check_call` function was not included in the original release. If you have to be compatible that far, use `subprocess.call()` and live with its flaws, or reimplement `check_call` using bare-bones `p = subprocess.Popen(...) ; result = p.wait(); if result != 0: raise HorriblePanic()` – tripleee May 08 '19 at 06:24
-
1@MarkHarviston Even if your "workaround" worked for you, it still has inherent bugs. You want to avoid `shell=True` entirely, otherwise you're back in the same can of worms that infests `os.system()`. See also https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess – tripleee May 08 '19 at 06:30
-
-
-
@CharlieMartin It's not the "Python 2.4" that I was complaining about (that part was for identification purposes) but the "without escaping". That stands: we shouldn't be showing practices with shell injection vulnerabilities. – Charles Duffy Jan 06 '23 at 11:06
I prefer:
os.startfile(path, 'open')
Note that this module supports filenames that have spaces in their folders and files e.g.
A:\abc\folder with spaces\file 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.

- 686
- 2
- 13
- 24

- 2,786
- 2
- 18
- 17
-
1Thanks. I didn't notice the availability, since the docs have it appended to the last paragraph. In most other sections, the availability note occupies its own line. – DrBloodmoney Jan 12 '09 at 17:14
-
1On Linux for some reason, rather than raising an error, the `startfile` function doesn't even exist, which means that users will get a confusing error message about a missing function. You might want to check the platform to avoid this. – c z Feb 14 '20 at 10:20
-
os.startfile supports pathlib like objects whereas other filename based solutions do not – Phillmac Oct 23 '20 at 10:10
Just for completeness (it wasn't in the question), xdg-open will do the same on Linux.
-
8+1 Usually, responders should not answer questions that were not asked, but in this case I think it is very relevant and helpful for the SO community as a whole. – demongolem Nov 03 '12 at 15:09
-
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])

- 1,052
- 14
- 34

- 217,122
- 57
- 293
- 297
-
2Huh, I didn't know about startfile. It would be nice if the Mac and Linux versions of Python picked up similar semantics. – Nick Jan 12 '09 at 18:27
-
3Relevant python bug: http://bugs.python.org/issue3177 - provide a nice patch, and it might get accepted =) – gnud Oct 18 '09 at 20:01
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.
-
3As far as I can tell, this is by far the best answer. Seems cross-platform and no need to check which platform is in use or import os, platform. – polandeer May 08 '13 at 23:37
-
2@jonathanrocher: I see [Mac support in the source code](https://github.com/python/cpython/blob/78d05eb847c6b8fede08ca74bb59210c00e4c599/Lib/webbrowser.py#L626). It uses `open location` there that should work if you give the path as a valid url. – jfs Aug 07 '15 at 19:14
-
1macOS: `import webbrowser webbrowser.open("file:///Users/nameGoesHere/Desktop/folder/file.py")` – Dec 17 '17 at 22:53
-
4https://docs.python.org/3/library/webbrowser.html#webbrowser.open "Note that on some platforms, trying to open a filename using [webbrowser.open(url)], may work and start the operating system’s associated program. However, this is neither supported nor portable." – nyanpasu64 Mar 25 '19 at 22:39
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.

- 1,062
- 1
- 17
- 39

- 42,675
- 9
- 40
- 30
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.

- 1,249
- 15
- 28

- 322
- 5
- 7
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\\Desktop\file.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.

- 137
- 2
- 1
-
1Another workaround is to give it a title in quotes, e.g. `start "Title" "C:\long path to\file.avi"` – user3364825 May 21 '14 at 13:45
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))

- 2,673
- 3
- 27
- 34

- 31
- 1
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.

- 15,241
- 22
- 66
- 83
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))

- 578
- 6
- 8
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]")
I built a small library combining the best answers here for cross-platform support:
$ pip install universal-startfile
then launch a file or URL:
from startfile import startfile
startfile("~/Downloads/example.png")
startfile("http://example.com")

- 11,699
- 10
- 66
- 90
I think you might want to open file in editor.
For Windows
subprocess.Popen(["notepad", filename])
For Linux
subprocess.Popen(["text-editor", filename])
I was getting an error when calling my open file() function. I was following along with a guide but the guide was written in windows while I'm on Linux. So the os.statrfile method wasn't working for me. I was able to alleviate this problem by doing the following:
Import libraries
import sys, os, subprocess
import tkinter
import tkinter.filedioalog as fd
import tkinter.messagebox as mb
After the lib imports I then called the subprocess method for opening a file in unix based OS which is "xdg-open" and the file that will be opened.
def open_file():
file = fd.askopenfilename(title='Choose a file of any type', filetypes=[('All files', "*.*")])
subprocess.call(['xdg-open', file])

- 1