15

Using Automator.app and Platypus.app, I have been able to bundle a simple shell script I created to power cycle wi-fi on my MacBook Pro. Both resulting apps run properly, but have one glaring issue that I want to correct: The apps reference the shell script from outside of the program. How can I embed the shell script and reference it from the app's resources so that the app can run even if the original source file is moved?

1

2 Answers 2

32

Just to mention it, if you Get Info on a script, you can set it to be opened with the Terminal. This will run the script when you double-click it.

Otherwise, packaging a script in a .app bundle is trivial. Mac OS X will happily run any script identified as the application's executable.

At a minimum, you need to following structure in place:

  • (name).app
    • Contents
      • MacOS
        • (name)

Where the file called (name) is your script (which must be executable, and must have a shebang line). (name) must be identical in the .app directory and the script file: for instance, if your app directory is called "My Shell Script.app", then the file inside the MacOS directory must be called "My Shell Script", with no extension.

If this is inconvenient, it's possible to use an Info.plist file to specify an alternate executable name. The Info.plist goes in the Contents directory:

  • Wrapper.app
    • Contents
      • Info.plist
      • MacOS
        • MyScript

This structure (a MyScript executable in a wrapper called Wrapper.app) works if you specify MyScript as the CFBundleExecutable in the property list:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleExecutable</key>
    <string>MyScript</string>
</dict>
</plist>

Using an Info.plist file is probably preferable, as that will allow you to rename your wrapper without breaking it.

Here's one example script that uses /bin/sh as the interpreter, but you really could have anything (#!/usr/bin/swift, #!/usr/bin/python, etc).

#!/bin/sh
open -a Calculator

The script will run as you double-click the app bundle.

You can bundle anything else that you need with your script within the Contents directory. If you feel fancy, you can reproduce the standard executable bundle layout with a Resources directory and things like that.

16
  • Isn't this file structure considered a PowerPC application? If so, they haven't been supported since 10.7 was released.
    – C1pher
    Commented Jun 11, 2015 at 23:13
  • As far as I know, the application's architecture has always been identified by executable headers, not by the bundle's structure, and PPC support was dropped at 10.6, without altering any of this. Either way, this works perfectly fine on my Yosemite setup, if you're worried.
    – zneak
    Commented Jun 11, 2015 at 23:16
  • 1
    That's hardly 391K. Is that all you have in there? Have you considered writing a new script from scratch with just that and the shebang line?
    – zneak
    Commented Jun 12, 2015 at 6:37
  • 5
    Though this works; I just want to note that you cannot code sign the bundle with the script in MacOS. Code signatures are not maintained for non-Mach-O files in the MacOS directory. See Apple's Code Signing Guide with more details: developer.apple.com/library/content/documentation/Security/…
    – Jason
    Commented Apr 11, 2018 at 17:59
  • 1
    @Daniel, if I’m not mistaken, macOS uses Apple Events to tell your program to open a document. You won’t be able to receive those from a shell script. It might be possible from scripting languages that can access native libraries.
    – zneak
    Commented Sep 13, 2020 at 8:40
0

While this works, there seems to be no way to access the Contents directory from the script. Its not passed in the environment variables to bash, or anywhere else I can find. This makes it hard to bundle stuff into the Contents directory since it seems to be inaccessible by the bash script. There are various hacks on the net, but they all involve effectively searching for the script by name, which means if there are two versions of the app in two places then it will fail. Anyone solve that?

2
  • 3
    What is the value of "$0" in the bash script? If it contains the full path, you could use dirname to get to the Contents folder. Commented Apr 29, 2020 at 15:28
  • 1
    I got it working with $(cd "$(dirname "$0")"; pwd) Commented Mar 1, 2021 at 18:11

Not the answer you're looking for? Browse other questions tagged or ask your own question.