How to create an OSX Application to wrap a call to a shell script?
My goal is to include in a zip file what amounts to a shortcut, instead of telling my customer(s) to open up Terminal and run a shell script.
My deployable essentially looks like this:
$ unzip Deliverable.zip
$ cd Deliverable; ls
app.jar run.sh
Script in run.sh
:
#!/bin/sh
java -jar app.jar
There's a lot more in the directory; suffice to say I need to run the script from the directory Deliverable since I need to access paths relative to it. However, I can't guarantee where a customer is going to open Deliverable.zip
(could be home directory, could be right in the Downloads directory, etc.)
I found this that describes how to create a new workflow in Automator, then save it as an application to launch a shell script. I tried to do that to wrap run.sh
but it says it can't find run.sh
.
Somebody suggested I use applescript and also sent me a link to how to use applescript to switch into the current directory; there's an applescript "action" in automator; so I made a new workflow with that, and saved it as an application. This is what that looks like:
The code:
on run {input, parameters}
tell application "Finder"
set current_path to container of (path to me) as alias
end tell
do shell script "java -jar app.jar"
return input
end run
And this is the error I get when I run it:
C'mon, this should be pretty simple. What I am doing wrong here?
Solution 1:
Rename your .sh file to .command and you can cd to the directory that the .command file is located in with the following at the beginning of the file:
cd "$(dirname $BASH_SOURCE)"
Solution 2:
I can see a couple of things wrong there.
Firstly you have opened a workflow and not an Application.
You should choose Application when you make your selection for the type of Automator file.
And the code you have will not work as you expect, since you have not changed directory. (cd).
In the code as you have it, all it is doing is getting the path as an alias and storing it in the variable current_path and in a format unsuitable for the unix commands.
But you do not use it.
So the current directory will most likely be your home folder
At this stage there is no telling what it is trying to launch.
If I run it as you have it I get.
Which makes sense since I do not have Java installed. But if I did I would not expect it to find the right file.
The Applescript need to look like this.
on run {input, parameters}
tell application "Finder"
set current_path to container of (path to me) as alias
end tell
do shell script "cd " & quoted form of (POSIX path of current_path) & " ;ls | open -fe"
return input
end run
In my example
do shell script "cd " & quoted form of (POSIX path of current_path) & " ;/bin/ls | /usr/bin/open -fe"
I cd to the POSIX path of the alias in the variable current_path
i.e from "alias "Macintosh HD:Applications:"" to "/Applications/"
The quoted form of
escapes the path using quotes.
I have used the /bin/ls command and pipe it to the open in TextEdit stdin as a demonstration here so that you can test to see if you are getting to the area you expect.
You would use something like;
do shell script "cd " & quoted form of (POSIX path of current_path) & " ;\"java -jar app.jar\""
update:
A another way is just use pure Applescript.
on run {input, parameters}
tell application "Finder"
set current_path to container of (path to me)
set theFile to (current_path) as alias
open file "java -jar app.jar" of theFile
end tell
end run
Solution 3:
Path to Script
In your AppleScript, you need to change the current working directory (cwd) before issuing the java command. Do this with an AppleScript line like:
do shell script "cd " & quoted form of (POSIX path of file_path) & " && java -jar app.jar"
The &&
is important. If the cd
succeeds, the java
command will be launched within the right cwd. If cd
fails, the java
command will not be run.
Problems you will likely encounter include:
- escaping the POSIX path passed to
cd
; users will have oddly named folders and spaces in their paths. - the
cd
may fail; wrap up your AppleScript in a try block to catch some errors and warn the user.
perl
Personally, I would wrap use a short perl script in place of a bash script.
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin qw($Bin); # $Bin is a path to the script's parent folder
`cd "$Bin" && java -jar app.jar`;
There are much better ways to write this perl snippet but this should work.
Automator Application
The best approach is to work through the problems with your Automator approach. @markhunte's answer discusses how to fix the path and create an application. This should get you most of the way.
Also see AppleScript path relative to script location.
appify — create the simplest possible Mac app from a shell script
Alternatively, you can use Thomas Aylott's appify script to bundle your shell script into an OS X application. Mathias Bynen's article walks through how to use the script, how to create simple Mac apps from shell scripts.
#!/bin/bash
if [ "$1" = "-h" -o "$1" = "--help" -o -z "$1" ]; then cat <<EOF
appify v3.0.1 for Mac OS X - http://mths.be/appify
Creates the simplest possible Mac app from a shell script.
Appify takes a shell script as its first argument:
`basename "$0"` my-script.sh
Note that you cannot rename appified apps. If you want to give your app
a custom name, use the second argument:
`basename "$0"` my-script.sh "My App"
Copyright (c) Thomas Aylott <http://subtlegradient.com/>
Modified by Mathias Bynens <http://mathiasbynens.be/>
EOF
exit; fi
APPNAME=${2:-$(basename "$1" ".sh")}
DIR="$APPNAME.app/Contents/MacOS"
if [ -a "$APPNAME.app" ]; then
echo "$PWD/$APPNAME.app already exists :("
exit 1
fi
mkdir -p "$DIR"
cp "$1" "$DIR/$APPNAME"
chmod +x "$DIR/$APPNAME"
echo "$PWD/$APPNAME.app"
Community contributed improvements to this script are available:
- appify - modified plist layout
- appify - adds an application icon
Code Signing
Once you have created your application bundle, it should be code signed. A code signed app will launch without requiring your clients to disable Gatekeeper.
Code sign your application using your Apple Developer ID and the codesign
command:
codesign -s <identity> -v <code-path> …
Code signing is a security technology, used in OS X, that allows you to certify that an app was created by you. Once an app is signed, the system can detect any change to the app—whether the change is introduced accidentally or by malicious code.
Learn more about code signing on the Apple Developer site.