7

In macOS, applications can be launched from the command line with open -a < application name>.

I would like to know if there is a command that returns the executable within the application bundle, when the application is not running, I'm hoping for something like this:

awesomeCommand <application name>
/Applications/***/MacOS/Content/application_executable
0

3 Answers 3

13

I would like to know if there is a command that returns the executable within the application bundle

There is no such command, but I've written a shell script that does exactly that for any application bundle in the file system of your startup disk.

It uses several command line tools included in macOS and leverages Apple's documentation on Launch Services, Application Bundles and Core Foundation Keys. The script does the following:

  1. It searches /Applications for the app bundle.
  2. If the app bundle can not be found there, the script queries the Launch Services database, only considering app bundles on the startup disk.
  3. If an app bundle is found, the script inspects the bundle's Info.plist and looks for a key namedCFBundleExecutable. This key stores the executable file (see below in the background information section for more on that). Otherwise, print an error message an exit.
  4. Prints the path to the executable to standard output.

The script understands both an application name with or without its extension, checks arguments, displays a usage message and a short description if run without arguments and returns an exit status with an error message if necessay.

Since the script searches /Applications and may do a time-consuming query to Launch Services, it isn't super fast (from ~ 1 sec to several seconds), but it was 100% accurate in my tests.

This is the script, with explanations on what is done in every step:

#!/bin/bash
# https://apple.stackexchange.com/a/334635
# Variables    
app_name="";
app_path_and_name="";
path_to_lsregister="/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/";

# If run without arguments, issue a usage summary and exit
if [[ "$1" == "" ]]; then
    echo "$(basename $0): returns name of bundle applications’s executable file";
    echo "usage: $(basename $0) [application name]";
    exit 0;
fi;

# If argument doesn't end with '.app', append it
if [[ "$1" =~ \.app$ ]]; then
    app_name="$1"
else
    app_name="$1.app";
fi;

# Look for the path of the application bundle
# Search /Applicatinos first
app_path_and_name="$(find /Applications -type d -name "$app_name" -maxdepth 5 | fgrep -m 1 "$app_name")";
# If not found, search the the LaunchServices database (this is the time-consuming task)
test "$app_path_and_name" || app_path_and_name="$($path_to_lsregister/lsregister -dump | fgrep -v '/Volumes|/System' | egrep --max-count 1 "path: */.*/$app_name " | sed 's:.* \(/.*app\) .*:\1:')"
#echo $app_path_and_name
# Check if Info.plist exists and is readable
if [[ -r "$app_path_and_name/Contents/Info.plist" ]]; then
    # Extract the CFBundleExecutable key that contains the name of the executable and print it to standard output
    echo "$app_path_and_name/MacOS/Contents/$(defaults read "$app_path_and_name/Contents/Info.plist" CFBundleExecutable)";
    exit 0;
else
    echo "Application '$1' not found";
    exit 1
fi

You can save the file (for example as appath) and make it executable as follows:

chmod a+x appath

If you prefer a function, use this instead:

function appath ()
{ 
    # https://apple.stackexchange.com/a/334635
    # Variables
    local app_name="";
    local app_path_and_name="";
    local path_to_lsregister="/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/";

    # If run without arguments, issue a usage summary and exit
    if [[ "$1" == "" ]]; then
        echo "$FUNCNAME: returns name of bundle applications’s executable file";
        echo "usage: $FUNCNAME [application name]"; 
        return 0;
    fi;

    # If argument doesn't end with '.app', append it
    if [[ "$1" =~ \.app$ ]]; then
        app_name="$1"
    else
        app_name="$1.app";
    fi;

    # Look for the path of the application bundle
    # Search /Applicatinos first
    app_path_and_name="$(find /Applications -type d -name "$app_name" -maxdepth 5 | fgrep -m 1 "$app_name")";
    # If not found, search the LaunchServices database (this is the time-consuming step)
    test "$app_path_and_name" || app_path_and_name="$($path_to_lsregister/lsregister -dump | fgrep -v '/Volumes|/System' | egrep --max-count 1 "path: */.*/$app_name " | sed 's:.* \(/.*app\) .*:\1:')"
    # Check if Info.plist exists and is readable
    if [[ -r "$app_path_and_name/Contents/Info.plist" ]]; then
        # Extract the CFBundleExecutable key that contains the name of the executable and print it to standard output
        echo "$app_path_and_name/MacOS/Contents/$(defaults read "$app_path_and_name/Contents/Info.plist" CFBundleExecutable)";
        return 0;
    else
        echo "Application '$1' not found";
        return 1
    fi
}

Just add it to .bashrc, source it:

. .bashrc

to use the function.

If you need to execute it as a (rather long) "one-liner", use (replace <app name> with the name of the application without the .app extension):

bash -c "app_name=\"<app name>.app\"; app_path_and_name=\"\"; if [[ \"\$app_name\" == \"\" ]]; then exit 2; fi; app_path_and_name=\"\$(find /Applications -type d -name \"\$app_name\" -maxdepth 5 | fgrep -m 1 \"\$app_name\")\"; test \"\$app_path_and_name\" || app_path_and_name=\"\$(/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -dump | fgrep -v \"/Volumes|/System\" | egrep --max-count 1 \"/\$app_name \" | sed \"s:.* \(/.*app\) .*:\1:\")\"; if [[ -r \"\$app_path_and_name/Contents/Info.plist\" ]]; then echo \"\$app_path_and_name/MacOS/Contents/\$(defaults read \"\$app_path_and_name/Contents/Info.plist\" CFBundleExecutable)\"; exit 0; else echo \"Application \"\$app_name\" not found\"; exit 1; fi"

(You write in a comment that the "command has to be launched from within a QProcess"). You could do the following:

process.start("bash", QStringList() << "-c" << "app_name=\"<app name>\.app"; ...");

See https://stackoverflow.com/a/20901985 for more information.)

Examples:

$ appath
appath: returns name of bundle applications’s executable file
usage: appath [application name]
$ appath "Microsoft Edge"
Application 'Microsoft Edge' not found
$ appath Finder
/System/Library/CoreServices/Finder.app/MacOS/Contents/Finder
$ appath "Simulator.app"
/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/MacOS/Contents/Simulator
$ appath "BalTax 2019"
/Applications/BalTax 2019/BalTax 2019.app/MacOS/Contents/JavaApplicationStub
$ appath ColorSync\ Utility
/Applications/Utilities/ColorSync Utility.app/MacOS/Contents/ColorSync Utility

BACKGROUND INFORMATION

The structure of a macOS bundle app bundle is well-known and documented by Apple.

According to The Structure of a macOS Application Bundle, the basic structure of a Mac app is:

MyApp.app/
   Contents/
      Info.plist
      MacOS/
      Resources/ 

where the MacOS folder "contains the application’s standalone executable code. Typically, this directory contains only one binary file with your application’s main entry point and statically linked code. However, you may put other standalone executables (such as command-line tools) in this directory as well."

The Info.plist file is required for the Finder to recognize an application bundle as such. This information property list filecontains XML property-list data that identifies the configuration of your bundle. The key that is of interest for us is CFBundleExecutable, which stores "the name of the main executable file. This is the code that is executed when the user launches your application."

7
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – bmike
    Sep 3, 2018 at 7:27
  • 1
    I had to change line 29 of the scrip to to include Contents in the path being returned.
    – Iain
    Apr 25, 2022 at 2:13
  • @Iain Thank you for the heads up, I've corrected and expanded the script/function/one-liner to make them work with system apps (like Finder and Terminal) in Monterey.
    – jaume
    Apr 25, 2022 at 9:50
  • 1
    I had to change the order from /MacOS/Contents/ to /Contents/MacOS/
    – user357269
    Nov 17, 2022 at 13:20
  • 1
    @user357269 I'm on 12.6.1 and can't reproduce the issue (script, function and one-liner work without modifications) but I'm glad it works for you, even if you had to change the order from /MacOS/Contents/ to /Contents/MacOS/.
    – jaume
    Nov 18, 2022 at 16:18
5

Not exactly a single command but I can give you the steps to get the information. This information is from Apple's Bundle Programming guide.

You need to know the structure of an Application.

In its root directory is has a directory Contents. In that directory there is a directory MacOS which contains application’s standalone executable code and a file Info.plist

Info.plist is a plist formatted file that contains info about the application including the name of the main executable file. This name is the value corresponding to the key CFBundleExecutable.

Thus the algorithim to find the applications starting executable is

  1. Find the application
  2. Find the Contents directory of the application
  3. Read the Info.plist file getting the value of CFBundleExecutable
  4. the executable is in the MacOs directory of 2 and is named the result of 3.

Note steps 1 and 3 are non trivial and require calls to Apple's system libraries

0
0

My answer is the same as @jaume but fixed the error at line #31 where he reverses the order of /Contents/MacOS/ and puts /MacOS/Contents/ which if bad of course.

This also detects if the app is designed for Mobile operating systems like iPhone or iPad (or whichever others are equivalent). Some of those apps are available directly from the Apple Store or can be installed by sideloading:

How to launch iOS/iPadOS apps from shell in macOS with Apple Silicon?

This is just the shell script the rest just needs similar modifications.

#!/bin/bash
# https://apple.stackexchange.com/a/334635
# Variables    
app_name="";
app_path_and_name="";
path_to_lsregister="/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/";

# If run without arguments, issue a usage summary and exit
if [[ "$1" == "" ]]; then
    echo "$(basename $0): returns name of bundle applications’s executable file";
    echo "usage: $(basename $0) [application name]";
    exit 0;
fi;

# If argument doesn't end with '.app', append it
if [[ "$1" =~ \.app$ ]]; then
    app_name="$1"
else
    app_name="$1.app";
fi;

# Look for the path of the application bundle
# Search /Applicatinos first
app_path_and_name="$(find /Applications -type d -name "$app_name" -maxdepth 5 | fgrep -m 1 "$app_name")";
# If not found, search the the LaunchServices database (this is the time-consuming task)
test "$app_path_and_name" || app_path_and_name="$($path_to_lsregister/lsregister -dump | fgrep -v '/Volumes|/System' | egrep --max-count 1 "path: */.*/$app_name " | sed 's:.* \(/.*app\) .*:\1:')"
#echo $app_path_and_name
# Check if Info.plist exists and is readable
if [[ -r "$app_path_and_name/Contents/Info.plist" ]]; then
    # Extract the CFBundleExecutable key that contains the name of the executable and print it to standard output
    echo "$app_path_and_name/Contents/MacOS/$(defaults read "$app_path_and_name/Contents/Info.plist" CFBundleExecutable)";
    exit 0;
# Check if it's an app for Apple mobile devices compatible with Apple Silicon desktops: iOS, iPadOS, possibly (not tested) watchOS and tvOS, so on
# https://apple.stackexchange.com/questions/452340/how-to-launch-ios-ipados-apps-from-shell-in-macos-with-apple-silicon
elif [[ -r "$app_path_and_name/WrappedBundle" ]] || [[ -r "$app_path_and_name/Wrapper" ]]; then
    echo -n "'"
    echo -n "$(basename "$app_path_and_name" .app)"
    echo -n "'"
    echo " is a mobile native app, launch with:";
    echo "open -a '$app_path_and_name'"
    exit 0;
else
    echo "Application '$1' not found";
    exit 1
fi

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .