Programmatically/Script-atically changing the default Open-With setting

Is there a way to change what app opens a file type programmatically/with a script?

Basically, sometimes I'm working on a web site, and I want to set all web files to open with a text editor (*.php, *.html, *.htm, etc...).

However, other times, I want to just view the files, so I want them to open with a browser.

Right now, I'm dragging items onto dock icons, which works, but is slow, particularly when I'm going through a large number of files with the keyboard only.

Basically, what I want is a small applescript/whatever that changes all the open-with settings.
That way, I can have one script for each open-with program, and change back and forth.

Thanks.


This is doable, but probably isn't as straightforward as you might think. You'll need to get very familiar with Uniform Type Identifiers. Look at Wikipedia's Uniform Type Identifier page.

OS X stores information on preferred file associations in a preference file with the name com.apple.LaunchServices.plist. Before you go try to find and modify that file, I suggest you familiarize yourself with OS X's domain hierarchy for defaults (a.k.a. "settings"). A decent article on this can be found here. (Disclaimer: they seem to be selling something on that site. I don't know what it is and have no association with them, the explanation is just a good one.)

Now that you know all about defaults and UTIs (er, not the medical kind), now we can talk about setting file associations from a script/command line.

First, you'll need to know the proper way to identify the files for which you want to make an association.

Remember how I said UTIs were important? There are multiple ways to identify a file. It depends on if the type has been formally declared on your system or not. For example, decent text editors like TextMate or TextWrangler will add quite a few type declarations to the type hierarchy when you use them on your system. If, however, you don't have those applications, you may not have those types declared.

OK, enough talk. Examples:

Get the UTI for a file:

$ mdls myFile.xml
...
kMDItemContentType             = "public.xml"
kMDItemContentTypeTree         = (
    "public.xml",
    "public.text",
    "public.data",
    "public.item",
    "public.content"
)
...

Ok, cool. An explicit content type we can use. Write that down somewhere.

$ mdls myFile.myExtn
...
kMDItemContentType             = "dyn.ah62d4rv4ge8048pftb4g6"
kMDItemContentTypeTree         = (
    "public.data",
    "public.item"
)
...

Oops. OS X doesn't know about ".myExtn" files. So, it created a dynamic UTI that we can't use for anything. And the parent types are too generic to be useful.

Now that we know what our files are, lets look at the LaunchServices.plist file and see what we can do:

$defaults read com.apple.LaunchServices
{
    ...
    LSHandlers =     (
                {
            LSHandlerContentType = "public.html";
            LSHandlerRoleAll = "com.apple.safari";
            LSHandlerRoleViewer = "com.google.chrome";
        },
    ...
                {
            LSHandlerContentTag = myExtn;
            LSHandlerContentTagClass = "public.filename-extension";
            LSHandlerRoleAll = "com.macromates.textmate";
        },
    ...
    );
    ...
}

So, when you have a "good" content type to use, the first construct is better. Otherwise the other construct. Note, there are other constructs in that file, but they aren't relevant to what you asked. Just know they are there when you look through the output.

As you can see, you'll need to find the UTI for the application you want to use. The UTIs for Safar and TextMate are in my example above, but to generically find the UTI for an application:

$ cd /Applications/MyApp.app/Contents
$ less Info.plist
...
        <key>CFBundleIdentifier</key>
        <string>com.apple.Safari</string>
...

NOTE: I have no idea what constitutes the difference between LSHandlerRoleAll and LSHandlerRoleViewer. I can't find documentation on that anywhere. What I do see is that 99% of the time LSHandlerRoleAll is the only one set (i.e. there is no LSHandlerRoleViewer at all) and that it is set to the UTI for the application that you desire to associate the type with.

Having brought you this far, I'm going to leave HOW to set the values you want as an exercise for the reader. Messing with these things can be somewhat dangerous. It is entirely possible for you to screw up a file and not have ANY of your file associations work. Then you have to throw away the file and start over.

Some hints:

  • Read up on defaults write and its syntax
  • Take a look at PlistBuddy. man PlistBuddy and /usr/libexec/PlistBuddy -h
  • Skip all this nonsense altogether and use RCDefaultApp

One option is to edit ~/Library/Preferences/.GlobalPreferences.plist:

defaults write com.apple.LaunchServices LSHandlers -array-add '{LSHandlerContentType=com.adobe.pdf;LSHandlerRoleAll=net.sourceforge.skim-app.skim;}'

You can use PlistBuddy to check if entries already exist, but I haven't found any way to apply changes without restarting or rebuilding the Launch Services database.

Using duti, you could run duti ~/.duti after saving this as ~/.duti:

net.sourceforge.skim-app.skim .pdf all

It doesn't really answer your question, but might be a solution.

The Inspector in Finder presents information for the currently selected file, or files:

++I

The Summary Info window of Finder is useful when multiple files are selected:

^++I

If you select multiple files of the same type, then either method above will allow you to easily switch the Open with: property for those files.


Not an answer to your exact question, but another possible solution. You can open a document with a specific application from the command line with the -a argument to open.

For example, open all the html files in the current directory.

> open -a 'Google Chrome' *.html

Open index.html and the javascript controller files in the Atom text editor:

> open -a 'atom' index.html js/controllers/*.js

Depending on your needs, you could modify this to be an Automator service which could be assigned a shortcut key, taking in the selected file paths as an argument.

Here's an example Automator AppleScript that receives files as input and opens the selected files in Chrome:

on run {input, parameters}
set openFiles to "open -a 'Google Chrome' " --note the trailing space
set filePaths to {}

--covert the filePaths to posix style
repeat with i from 1 to count of input
    set aFile to input's item i as alias
    set aFile to quoted form of POSIX path of aFile
    set filePaths's end to aFile
end repeat

--convert filePaths list to a string delimited by spaces
set tid to text item delimiters
set text item delimiters to " "
set filePaths to filePaths as text
set text item delimiters to tid

--Open files via commandline
do shell script openFiles & filePaths
return input

end run

Obviously, you could change the app name "Sublime" text editor, save it as another service, and assign them both shortcut keys.