In this series of posts, I will explore the many ways that you can launch a script on macOS. In the previous, inaugural post, I described what happens when you launch a script from an interactive terminal shell.
There are several virtual terminal applications available for macOS. iTerm is very popular. Some text editors like Visual Studio Code and Nova, have terminals built-in. Since the actual launching of an executable is done by the shell running inside the virtual terminal, the launch process remains the same.
That said, Terminal app has a useful trick up its sleeves.
command file extension
When you change the file extension of a script to
.command, double-clicking the file will open it in a new Terminal window and run it there. Any input or output the script requires will happen in that Terminal window. When the script exits, the shell session in the Terminal window will exit.
Let’s take this simple script:
#!/bin/sh echo "Enter your name: " read -r username echo "Hello, $username"
When you put this this in a
.command file and double-click it, you get a new Terminal window with:
/Users/armin/Desktop/hello_name.command ; exit; ~ % /Users/armin/Desktop/hello_name.command ; exit; Enter your name:
You can see that Terminal opens a new window with a new, default shell and all your configurations, then launches the script right away. The script prints its output and then waits for the user input (the
read command). When you enter the name at the prompt, the script continues.
Armin Hello, Armin Saving session...completed. [Process completed]
When the script ends, the shell in the Terminal window exits, as well. No more interactive prompt will be shown.
This script expects user input in the Terminal and then presents output to stdout in the same window. While you could re-write a script to use AppleScript’s
display dialog to handle both in the input and the output, it would make the script significantly more complex.
Instead, you can change the file extension to
.command and then a double-click will create a new Terminal window where the user interaction (input and output) takes place. For the right kind of user and workflow, this can be a sufficient solution with practically no overhead.
You can also remove the file extension completely. The behavior when you double-click such a file in Finder will be the same. Extension-less executables also get a different the icon. Either way, you need to have the executable bit set for the script.
Note: iTerm can also open .command files, but I have had some trouble with user interaction in these cases. Since I usually don’t use iTerm, maybe I have something setup wrong?
Quarantine and Gatekeeper
A Terminal window is not a user interface that many users will appreciate, but this allows you share scripts with other users in a form they understand. “Double-click this to run” is something that fits with most users’ idea of how macOS works.
Before you start creating dozens of
.command scripts and share them, there is a major tripwire that macOS security has set up.
When you share an executable file through a website, email or a chat message, macOS will attach a quarantine flag. With applications, this flag triggers a GateKeeper scan before the app is launched and it will show the standard dialog, even when the app is signed and notarized and a much more “scary” warning when it is not.
When you launch a command or script from Terminal, either directly or indirectly with a double-click, and it still has the quarantine flag set, it will not launch. You will not get one of the standard Gatekeeper dialogs. Just an opaque
operation not permitted error in the Terminal output.
You can check if a file has the quarantine flag set with the
> xattr hello_name.command com.apple.TextEncoding com.apple.lastuseddate#PS com.apple.quarantine
Your list list of extended attributes may be different. The quarantine flag has the label
xattr command also can remove the quarantine flag:
> xattr -d com.apple.quarantine hello_name.command
Apple seems to assume that when you are using Terminal, you know what you are doing. That means you can bypass most of the mechanisms that attach a quarantine flag with command line tools. When you download something with
curl it won’t get quarantined. You can install an unsigned, unnotarized pkg installer using the
installer command. Because this is possible, it doesn’t mean it is always wise. Piping a
curl command directly into
bash or any interpreter is still poor security.
Most of the time though, a script file shared to another Mac or another user will almost certainly get the quarantine flag. Users who are comfortable with using Terminal should be able to use the
xattr command to disable this protection, but this is not something for ‘normal’ users. So quarantine, makes the use of the command file extensions far less effective than it could be. This is probably intentional, since executables that are opened by double-click can be an easy way to sneak malware and other unwanted software onto a system, leveraging a user’s ignorance of what is happening.
This is generally true when you move scripts and executable files between macOS systems. I have also seen the quarantine flag getting set when you store a script in a cloud sync service (especially iCloud) or when you edit an executable with a sandboxed application.
One way around the quarantine, would be to distribute and properly install the scripts with an installer pkg. Then you can properly sign and notarize the installer pkg and the scripts will not be quarantined, as they come from a trusted and verified source. This may be a good solution for some workflows, but generally feels a bit “over-designed.”
Output flashes by so quickly
There is a setting in Terminal’s preferences which determines what happens with windows when the shell exits. You can find it under the ‘Shell’ tab in the ‘Profiles’ area. This setting can be different for each profile. Under ‘When the shell exits’ there is a popup menu with the options ‘Close the window,’ ‘Close if the shell exited cleanly,’ and ‘Don’t close the window.’ The last ‘Don’t close’ is the default.
When you have this option set to ‘Close the window,’ the new Terminal window from a command file might only be active and visible for a short time. This may or may not be a good thing, depending on what you want.
Script files with the command file extension can be a simple, straightforward way to make scripts easily ‘launchable’ from Finder. You can also put them in the Dock or in the Login Items. The user experience is, well, a terminal, so not terribly nice, but it can be useful, and does not require any modification of the script.
When you share executable scripts, whether they have the command file extension or not, Gatekeeper quarantine on macOS can prevent the script from running. You should get familiar with the quarantine flag and the
xattr command to manipulate it.
In the next post, we launch shell scripts from AppleScripts.]