How do I close a window from an application, passing the file name?

I’m trying to produce an Applescript-based shell command that tells the Preview application from Mac OS X to close a particular window.

#!/bin/sh

osascript <<EOF
tell application "Preview"
   close "$1"
end tell
EOF

But this doesn’t work : I get the error message

25:52: execution error: Preview got an error: "musixdoc.pdf" doesn’t understand the close message. (-1708)

Related question: How do I close an OS X application from the command line using a alias defined in my .bash_profile?


Getting Preview.app to accept AppleScript commands

By default, AppleScripting Preview won't work because Preview is missing the necessary dictionary. To fix this, check Lauri's answer here, which explains setting NSAppleScriptEnabled for Preview.app.

Quit Preview.app, then open a terminal and enter:

sudo defaults write /Applications/Preview.app/Contents/Info NSAppleScriptEnabled -bool true
sudo chmod 644 /Applications/Preview.app/Contents/Info.plist
sudo codesign -f -s - /Applications/Preview.app


Closing a window from an application

1) By window index or name of the window

The command to close a window of any named application would be something like this:

tell application "Preview" to close window 1

… or if you want to close a named document window, e.g. foo.jpg:

tell application "Preview" to close (every window whose name is "foo.jpg")

So, in your shell script that'd be:

#!/bin/sh
osascript <<EOF
tell application "Preview"
  close (every window whose name is "$1")
end tell
EOF

Here, the first argument passed to the script is the name of the window you want to close, e.g. ./quit.sh foo.jpg. Note that if your file contains spaces, you have to quote the filename, e.g. ./quit.sh "foo bar.jpg".

Or if you want to close arbitrary windows from any application, use this:

#!/bin/sh
osascript <<EOF
tell application "$1"
  close (every window whose name is "$2")
end tell
EOF

Here, you'd use ./quit.sh Preview foo.jpg for example.

2) By file name

If you want to close a window that belongs to a certain document, but supplying the file name, you need something else. This is because a multi-page PDF could be displayed as foo.pdf (Page 1 of 42), but you'd just want to pass foo.pdf to the AppleScript.

Here we iterate through the windows and compare the filenames against the argument passed to the script:

osascript <<EOF
tell application "Preview"
    set windowCount to number of windows
    repeat with x from 1 to windowCount
        set docName to (name of document of window x)
        if (docName is equal to "$1") then
            close window x
        end if
    end repeat
end tell
EOF

Now you can simply call ./quit.sh foo.pdf. In a generalized fashion, for all apps with named document windows, that'd be:

osascript <<EOF
tell application "$1"
    set windowCount to number of windows
    repeat with x from 1 to windowCount
        set docName to (name of document of window x)
        if (docName is equal to "$2") then
            close window x
        end if
    end repeat
end tell
EOF


Caveat: Auto-closing Preview.app

Preview.app is one of these applications that automatically quits once its last document window is closed. It does that in order to save memory and "clean up". To disable this behavior, run the following:

defaults write -g NSDisableAutomaticTermination -bool TRUE

Of course, to undo that, change TRUE to FALSE.


Using functions instead of scripts

Finally, I'd suggest putting your scripts into a function that is always available in your shell. To do this, add the scripts to your ~/.bash_profile. Create this file if it doesn't exist.

cw() {
osascript <<EOF
tell application "$1"
    set windowCount to number of windows
    repeat with x from 1 to windowCount
        set docName to (name of document of window x)
        if (docName is equal to "$2") then
            close window x
        end if
    end repeat
end tell
EOF
}

Once you save your bash profile and restart the shell, you can call cw Preview foo.pdf from everywhere.