Basic Syntax and What open Actually Does
At its core, `open` passes a file, directory, or URL to the macOS Launch Services framework, which decides what application to use - the same mechanism that kicks in when you double-click something in Finder. That means `open foo.pdf` behaves identically to double-clicking foo.pdf in Finder: it uses whatever app is registered as the default handler for PDF files.
The binary accepts paths, URLs, bundle identifiers, and application names. It returns immediately by default, meaning it does not block your shell session. If you need to block until the opened application exits, use the `-W` flag.
Unlike `xdg-open` on Linux, `open` is tightly integrated with Spotlight metadata and Launch Services, so it correctly handles quarantine flags, sandboxed apps, and code-signed bundles without special handling on your part.
open /path/to/file.pdf
open https://myunix.org
open .
Opening Files with a Specific Application
The `-a` flag lets you specify which application to use, bypassing the default handler. You can pass either the application name (as it appears in /Applications) or the full path to the .app bundle.
This is useful when you maintain multiple versions of an editor, need to open a config file in a text editor instead of its registered handler, or want to force a specific browser. We regularly use this on our test server to open JSON response files directly in a browser for quick inspection without changing the system-level file association.
Note that `-a` performs a case-insensitive match against the application name, so `open -a visual studio code` works the same as `open -a 'Visual Studio Code'`.
# Open a file in TextEdit instead of default handler
open -a TextEdit /etc/hosts
# Open with full path to .app bundle
open -a /Applications/Sublime\ Text.app myconfig.conf
# Force a specific browser
open -a Firefox https://myunix.org
# Open with VS Code
open -a 'Visual Studio Code' .
Opening Applications Without a File Argument
Passing `-a` alone with no file argument launches the application itself, equivalent to clicking its icon in the Dock. If the app is already running, macOS brings it to the foreground without launching a second instance - unless the application explicitly supports multiple instances.
To force a new instance regardless of whether one is already running, add the `-n` flag. We use this when profiling application startup time, running two configurations of the same app simultaneously, or testing plugin behavior in a fresh process.
# Launch or focus an app
open -a Safari
# Force a new instance of the app
open -n -a Safari
# Useful for terminal emulators
open -n -a iTerm
Opening Directories and Revealing Files in Finder
Passing a directory path to `open` opens it in Finder. `open .` is the fastest way to pop open the current working directory, and we have it aliased everywhere.
The `-R` flag (reveal) is more precise: instead of opening a directory, it highlights the specified file or directory in its parent Finder window. This is what Finder's "Show in Finder" button does internally. It is particularly useful in scripts that generate output files and need to surface them to a user.
The difference matters: `open /path/to/file.txt` opens the file with its associated application, while `open -R /path/to/file.txt` opens the parent folder and selects that file.
# Open current directory in Finder
open .
# Open specific directory
open ~/Downloads
# Reveal a file in Finder (select it without opening)
open -R ~/Documents/report.pdf
# Reveal a build artifact after compilation
open -R ./build/output/app.dmg
Opening URLs and Custom URL Schemes
Any argument starting with a scheme that Launch Services recognizes gets treated as a URL. Standard http and https open in the default browser. Custom schemes like `vscode://`, `x-man-page://`, and `itms-apps://` route to the registered handler for that scheme.
This is powerful for scripting. If you are building a deploy script that should open a monitoring dashboard on successful deploy, `open https://grafana.example.com/d/abc123` does exactly that without hardcoding a browser. On our CI runner we skip this since there is no display server, but on developer workstations it is a useful quality-of-life touch.
The `x-man-page://` scheme is worth knowing: it opens man pages in the macOS Terminal man page viewer, which renders them with proper formatting and search.
# Open default browser
open https://myunix.org
# Open man page in graphical viewer
open x-man-page://rsync
# Open VS Code to a specific file via URL scheme
open 'vscode://file/Users/you/project/main.go:42'
# Open App Store page
open 'macappstores://apps.apple.com/app/id497799835'
Using open with stdin: The -f Flag
The `-f` flag tells `open` to read from stdin and open the content in the default text editor. This is niche but genuinely useful: pipe any command output directly into TextEdit (or whichever editor handles plain text) for quick inspection without writing a temp file manually.
We use this when reviewing long command outputs that would be inconvenient to scroll through in a terminal - log files, JSON blobs, git diffs. The content opens in a new untitled document; nothing is written to disk unless you explicitly save.
Combine `-f` with `-a` to send stdin to a specific editor instead of the system default.
# Open command output in default text editor
git log --oneline -50 | open -f
# Send to a specific editor
curl -s https://api.example.com/status | python3 -m json.tool | open -f -a 'Sublime Text'
# Review a large config diff
diff /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak | open -f
Blocking Until the App Closes: The -W Flag
By default, `open` exits immediately after handing off to Launch Services. The `-W` flag changes this: your shell blocks until the opened application quits. This turns `open` into a synchronous step you can embed in scripts.
The practical use case is editor integration in shell scripts. If you want a script to pause while the user edits a config file and then continue processing once they close the editor, `-W` is the mechanism. It is similar to how `git commit` opens `$EDITOR` and waits.
Note that `-W` waits for the application to quit entirely, not just close the document. If the user opens TextEdit, edits your file, closes the window but leaves TextEdit running, the flag does not release. Combine `-W` with `-n` to force a fresh instance, which ensures you are waiting on a dedicated process that will quit when the user closes the document.
# Block until user closes the app
open -W -a TextEdit myconfig.txt
echo "User finished editing, continuing..."
# Best practice: force new instance so -W tracks this specific process
open -W -n -a TextEdit myconfig.txt
# Script pattern: edit, then process
open -W -n -a 'Sublime Text' ./deploy.conf
bash ./deploy.sh --config ./deploy.conf
Finding Bundle Identifiers with osascript and mdfind
When using `open -b`, you need the application's bundle identifier rather than its name. Bundle identifiers follow reverse-DNS notation (e.g., `com.apple.Safari`, `com.microsoft.VSCode`) and are stable across application renames, unlike display names.
Two quick ways to find them: `mdls` reads the metadata from the .app bundle, and `osascript` can query running apps. We prefer `mdls` for installed apps and keep a grep alias for it.
Bundle identifiers matter for scripts that need to be robust: if a user renames their copy of an application, `-a AppName` breaks, but `-b com.example.app` does not.
# Get bundle identifier from .app bundle
mdls -name kMDItemCFBundleIdentifier /Applications/Safari.app
# Output: kMDItemCFBundleIdentifier = "com.apple.Safari"
# Open using bundle identifier
open -b com.apple.Safari https://myunix.org
# Find bundle ID of a running app via osascript
osascript -e 'id of app "Firefox"'
# Search all installed apps for a bundle ID pattern
mdfind "kMDItemContentType == 'com.apple.application-bundle'" | \
xargs -I{} mdls -name kMDItemCFBundleIdentifier {} 2>/dev/null | \
grep -i 'jetbrains'
Practical Shell Aliases and Functions
Three aliases we put in every macOS `.zshrc` or `.bash_profile` on any machine we set up:
`o` as a shorthand for `open`, matching the muscle memory most macOS power users already have. `finder` to open Finder at the current path. `preview` to open a file in Quick Look without launching a full application.
Beyond aliases, a shell function for a project launcher is worth having. If you run multiple projects and want a single command to open the directory in Finder, the project in your editor, and the localhost URL in a browser simultaneously, you can build that in about 10 lines.
# ~/.zshrc additions
alias o='open'
alias finder='open .'
alias preview='qlmanage -p'
# Project launcher function
launch_project() {
local dir="${1:-.}"
open "$dir" # Open in Finder
open -a 'Visual Studio Code' "$dir" # Open in editor
open http://localhost:3000 # Open dev server
}
# Usage
launch_project ~/projects/myapp
Integrating open into Build and Deploy Scripts
On developer workstations, `open` belongs in Makefiles and build scripts as a post-step that surfaces output to the engineer. A common pattern after building documentation is `open ./docs/_build/html/index.html`. After generating a code coverage report: `open ./coverage/index.html`. After building a macOS disk image: `open -R ./build/MyApp-1.4.0.dmg` to reveal it in Finder.
Keep these steps conditional on an environment variable or a CI detection check. Running `open` in a headless CI environment either silently fails or throws an error depending on the macOS version. The standard check is whether `TERM_PROGRAM` or `DISPLAY` is set, or more reliably, whether the process is running under a user session with a GUI.
For teams building automation around macOS workflows, tools like taskbotshub.ai handle macOS-specific automation steps including GUI-layer interactions, which is useful when you need to trigger `open` commands as part of a larger orchestration pipeline that spans multiple machines or CI stages.
If you are scripting project setup and part of that involves registering a domain or picking a project name before configuring local dev environments, nicename.me is worth checking for availability before you start baking hostnames into your configs.
# Makefile target with CI guard
build-docs:
sphinx-build -b html docs/ docs/_build/html
@if [ -z "$$CI" ]; then \
open docs/_build/html/index.html; \
fi
# Post-package script
make_dmg() {
hdiutil create -srcfolder ./build/MyApp.app \
-volname "MyApp" -format UDZO \
-o ./dist/MyApp-"$VERSION".dmg
if [ -z "$CI" ]; then
open -R ./dist/MyApp-"$VERSION".dmg
fi
}
Passing Arguments to Applications with --args
The `--args` flag passes everything after it directly to the target application as command-line arguments. This is the correct way to open an application with flags when you are going through `open` rather than launching the binary inside the .app bundle directly.
Anything before `--args` is interpreted by `open` itself. Anything after is forwarded verbatim to the app. This is how you open Chrome with a specific profile, launch an Electron app in dev mode, or start a JVM-based IDE with custom heap settings.
Note that not every GUI application responds to command-line arguments in a meaningful way - this depends entirely on how the app is built. Chromium-based apps, Electron apps, and most developer tools handle them correctly.
# Open Chrome with a specific profile
open -a 'Google Chrome' --args --profile-directory='Profile 2'
# Launch Chrome in incognito with a URL
open -a 'Google Chrome' --args --incognito https://myunix.org
# Open an Electron app in dev mode
open -a MyElectronApp --args --dev
# Pass JVM flags to a Java-based IDE (where supported)
open -a 'IntelliJ IDEA' --args -Xmx4g
Debugging: When open Fails or Uses the Wrong App
When `open` sends a file to the wrong application, the issue is usually a stale Launch Services database. The database is a cache of file type to application mappings, and it can get out of sync after application installs, uninstalls, or updates.
On macOS 12 and later, the correct command to rebuild it is `lsregister` with the `-kill`, `-r`, and `-domain` flags. On earlier versions you used `/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister`, but on current macOS it is available via the Spotlight helper path.
If a specific file extension is consistently opening in the wrong app, you can reassign it permanently with `duti` (installable via Homebrew), which writes to the Launch Services database directly rather than going through the Finder "Open With" UI.
# Rebuild Launch Services database
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/\
LaunchServices.framework/Versions/A/Support/lsregister \
-kill -r -domain local -domain system -domain user
# Install duti for fine-grained file associations
brew install duti
# Set VS Code as handler for .go files using bundle ID
duti -s com.microsoft.VSCode .go all
# Check current handler for an extension
duti -x go
# List all handlers registered for a UTI
/usr/bin/mdls -name kMDItemContentTypeTree /path/to/file.go