36

I'm trying to set up a few keyboard shortcuts that open specific iTerm sessions, which I was able to do with BetterTouchTool and a bit of AppleScript magic. The problem is that OS X insists to open a Terminal window for any shell script you execute through the GUI (i.e. from Finder, or as a keyboard shortcut from BetterTouchTool). The terminal window doesn't appear if I run the script directly from another terminal.

A workaround I found was to wrap the script in an .app directory, which solves the problem of the superfluous terminal window, but has some other issues (for example OS X seems to treat each resulting iTerm window as a separate app, cluttering my dock). (EDIT: this behaviour was actually caused by a bug in my script, see below)

I also tried assigning the Terminal app to another virtual desktop in the Spaces settings in an attempt to move it out of sight, but then it will just first switch to that desktop before running the script.

Is there a way to disable this behaviour completely? I already found the setting in the Terminal preferences to close the window after the script has finished, but it's still annoying to have the Terminal window pop up for a second.

5
  • Sorry, I don't quite understand. What do you want to do with those shell scripts? Do you want to open iTerm though a shell script? Are you exclusively talking about Terminal.app or mixing iTerm with "Terminal" in your question? Could you maybe post an example of what you're trying to run?
    – slhck
    Commented Nov 22, 2011 at 12:37
  • I've setup several iTerm sessions to launch a normal shell, open a Rails console, display the Rails log etc. I'm using iTerm exclusively, and Terminal is opened automatically by OS X for whatever silly reason. But anyway, I found a solution to make the bundle approach work properly (see edited question).
    – toupeira
    Commented Nov 22, 2011 at 14:20
  • I see! Well, it would be awesome if you could answer your own question (using the button below) and tell us what you did, or how you solved the issue. Maybe post some examples in your question, and then just add a brief answer. This way, if somebody stumbles upon your post, they might learn something from it!
    – slhck
    Commented Nov 22, 2011 at 14:25
  • Well I still don't have an answer for the actual question (i.e. how to avoid the Terminal completely, without having to use an .app wrapper), so I'd rather leave it open in case somebody can enlighten me.
    – toupeira
    Commented Nov 22, 2011 at 14:39
  • 1
    I'm still confused. You don't want to run them via an .app created by Automator?
    – slhck
    Commented Nov 22, 2011 at 15:03

7 Answers 7

33

Open Automator, choose Application, add a Run Shell Script action and put in your Shell command between quotes (if you have a file, you can just drag and drop it).

Other than playing it, now you can save it (as an app anywhere) and even set the icon.

5
  • I tried to run a command this way from a full-screen app, however it always opened it in the background. I had to background the command in the shell in a non-blocking fashion by appending a & and then call an AppleScript to bring the app (in my case mplayer) to the front: mplayer myvideo.avi &; osascript -e 'tell application "System Events" to set frontmost of the first process whose displayed name is "mplayer" to true'
    – Lenar Hoyt
    Commented Jan 20, 2015 at 14:39
  • Cool hack @mcb. But I don't get why. Usually we want to hide the terminal window because it will open another window, which is the main one. If you need mplayer's first terminal window, I suppose it doesn't open another one and so why would you want to hide it in the first place?
    – cregox
    Commented Jan 20, 2015 at 17:27
  • I’m using mplayer to play a video from within a slideshow (in Skim). There is a bug that the player stays in the background if Skim is set to fullscreen, I’m not sure though, whether this bug is only related to Skim or all fullscreen apps, so I figured I would post my solution here.
    – Lenar Hoyt
    Commented Jan 21, 2015 at 2:53
  • 1
    The "set the icon" link is broken Commented Jan 9, 2021 at 18:03
  • @gabriel thanks for reporting. it is broken indeed, no idea what that was sorry for failing this time my best updated personal recommendation is to ditch apple and go for linux or android. i don't even have a desktop for over 1 year now, and i love it (still coding, and everything, no root, just termux)! 😘
    – cregox
    Commented Jan 10, 2021 at 16:04
19

Here's a quick and dirty example with almost no effort (for an app named "myapp"):

  1. Make an partial applications hierarchy:

        mkdir -p ./myapp.app/Contents/MacOS
    
  2. Make sure the first line of your script has the full path to the needed program, e.g.,

    #!/bin/bash
    
  3. Name your shell script "myapp" (no quotes, no extension), give it execute permissions, and then put it in the MacOS sub-directory. To give it execute permissions:

    chmod ugo+x myapp
    
  4. Go to the Contents sub-directory and create a PkgInfo file containing the string: APPL???? [no line terminator at end of string!] Use the cat(1) utility to create the file:

    cat > PkgInfo
    APPL????
    

    After you have typed the string (do not hit return!), enter two control-D's, which will close
    the file with no line terminator and return you to the shell prompt.)

  5. Double-click your new "app" from the finder. It will run with no window.

7
  • 1
    Did not work on 10.13.6, error message says "You can’t open the application “LoginSessionTimeLimit.app” because it is not supported on this type of Mac." Commented Dec 26, 2018 at 15:16
  • 2
    Did work in Mojave (10.14.3) for me. I really liked this approach. Thanks.
    – loco.loop
    Commented Feb 28, 2019 at 22:50
  • Worked for me on 10.13.6. @Douglas Held try running your script "manually", i.e. from Terminal. Probably it exits w/ error hence it's not working for you...
    – marekful
    Commented Apr 18, 2019 at 9:32
  • Great! Tested working in Mojave 10.14.6 Commented Dec 13, 2019 at 7:33
  • 1
    Worked in Sonoma 14.2.1. One key thing that I would emphasize is to have full path for all binaries that you use in your shell script. Also, you can use echo -n "APPL????" > myapp.app/Contents/PkgInfo to create the PkgInfo file.
    – srgsanky
    Commented Jan 6 at 20:14
5

You might want to check out Platypus, which creates Mac OS X applications from shell scripts and other interpreted scripts.

3

Here's a small workaround, in case you fancy application launchers like Alfred. I use it every day, and I've bought the Powerpack, which allows you to run silent shell scripts.

enter image description here

These will not open a terminal when running and can be bound to any keyword sequence. They can even include parameters, and have additional options:

enter image description here

I use this for some small snippets, and it's very flexible.

1
2

If you add key LSUIElement and set it to 1 in Info.plist of your app, it will not create an icon in Dock.

Here is Info.plist of my small shell script app:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleExecutable</key>
    <string>launch</string>
    <key>CFBundleIconFile</key>
    <string>launch</string>
    <key>LSUIElement</key>
    <string>1</string>
</dict>
</plist>
4
  • Thanks, but this doesn't seem to have an effect. I also tried touching the bundle to make sure the newest version was used. But anyway, I don't really like the bundle approach because it seems it will always create separate app instances
    – toupeira
    Commented Nov 22, 2011 at 12:31
  • For me touch did not help also (I tried to switch off LSUIElement), there is some other sort of caching, but compressing and extracting application helped. Why don't you like bundle approach? Also you can use applescript and just run do shell script "say 'arsti'", this will not open terminal window.
    – tig
    Commented Nov 22, 2011 at 12:47
  • Okay, turns out I'm just an idiot ;-) I couldn't find a reliable way to check if an app is already running with AppleScript, so I used a normal bash script which calls pgrep, and then passes the relevant code directly to osascript (the AppleScript interpreter). The problem is that pgrep came from Homebrew, and isn't in the default $PATH. So my script always launched a new iTerm instance, even if it was already running. After adding the full path to pgrep in the script the bundle approach seems to work fine after all, so I think I'm happy for now ;-)
    – toupeira
    Commented Nov 22, 2011 at 14:18
  • Combining this answer with the one from @JeffB above, and with a simplified version of @tig's Info.plist (I removed the the CFBundleExecutable and CFBundleIconFile keys), I got this to work perfectly.
    – Dave Land
    Commented May 30, 2019 at 21:36
1

There is an other possible workout. Safe your shell script in a automator workflow. Invoke the workflow with launchd. The code is:

/usr/bin/automator /path/to/the/file.workflow

This will not launch any program nor show up in the menu bar and its way more silent then a Platypus app.

An even better way to execute shell scripts is direct in launchd.

/bin/bash /path/to/shellScript.command

will execute the command ultra silently in the background.

0

Here's a bit of a workaround:

Compile an applescript that calls a shell script :)

osacompile -e 'do shell script "cd ~; echo aaa > temp.txt"' -o ~/name_of_script.scpt

The osacompile will compile the 'do shell script "XXX"' applescript snippet. Your shell script is the XXX.

Watch out for quoting, the shell script has to be quoted properly to get past the shell you're using to compile it still intact.

But this can be run from BetterTouchTool without any whacky-ness.

You must log in to answer this question.

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