Quit applications politely from the command line

Aug 26, '09 07:30:02AM

Contributed by: Sesquipedalian

I often ssh from one Mac to another in order to manage applications on the remote machine. Thus, I find it frustrating that Mac OS X includes no easy way to quit an application politely (allowing the app to save data, clean up after itself, etc.) from the command line. There are command line options that are easy to use, but not nice to applications, and an option that is nice to applications, but not easy to use:

  1. Kill and killall are easy to use, but none of their possible signals will initiate a polite quit of an OS X application. All kill signals either force a quit without saving, or (worse) force a quit without saving and with an error message from the OS. This is because kill deals with processes, not applications.
  2. Osascript can use an Applescript command to initiate a polite quit using the form osascript -e "tell application "AppName" to quit". But it is tedious to write that command on the command line, especially if one has to do this sort of thing more than once in a blue moon.
So I made a way. Now I just type quit AppName on the command line, and the app quits nicely. The quit tool is a bash script, as follows:
#!/bin/bash

#-----------------------------------------------------------
# 
# Quit: quits Mac OS X applications politely
#
# Written by Jon Stovell, July 11, 2009
#
#-----------------------------------------------------------

#-----------------------------------------------------------
# This script takes one or more application names as 
# arguments, and uses osascript to tell each one to quit.
# Unlike kill and killall, this allows applications to save
# files and perform any necessary operations before exiting.
# 
# This script is not case sensitive.
# 
# Note: application names are NOT process names! The 
# application name is the name that Script Editor uses.
# Often the process name and the application name are
# identical, but not always.
#-----------------------------------------------------------

usage()
{
	echo "Usage: `basename $0` [-a] [-p] Application1 \"Application 2\" ..."
	echo ""
	echo "Arguments are the names of one or more applications."
	echo "Arguments are not case sensitive."
	echo "Arguments with spaces should be quoted."
	echo ""
	echo "Options:"
	echo "	-a	Match argument string with any of the application's"
	echo "		name, short name, title, or display name."
	echo "		E.g.: \`quit \"Microsoft Word\"\` and \`quit -a Word\`"
	echo "		will both quit Microsoft Word, because the app calls"
	echo "		itself \"Word\" in the menu bar."
	echo "	-p	Use partial matches (e.g. edit for TextEdit). Prompts"
	echo "		for confirmation."
	echo ""
	exit 65
}

searchall=1
is_contains="is"
while getopts "ap" opt
do
	case $opt in
	a) searchall=0 ;;
	p) is_contains="contains" ;;
	[?]) usage; exit ;;
	esac
	
	shift $(($OPTIND - 1)) # Decrements the argument pointer so it points to next argument. $1 now references the first non option item supplied on the command-line if one exists.
done

if [ -z $1 ]; then usage; fi

for arg in "$@"
do
	if [ searchall=0 ]
	then
		appname=`osascript -e "tell application \"System Events\" to return every application process whose (name $is_contains \"$arg\" or short name $is_contains \"$arg\" or title $is_contains \"$arg\" or displayed name $is_contains \"$arg\")"`
	else
		appname=`osascript -e "tell application \"System Events\" to return every application process whose name $is_contains \"$arg\""`
	fi
	
	if [[ -n $appname && $appname != *", "* ]] # found 1 matching application
	then 
		if [[ $is_contains == is ]]
		then
			osascript -e "ignoring application responses" -e "tell application \"$appname\" to quit with saving" -e "end ignoring"
		else
			echo "Choose the application to quit:"
			
			eval set $appname # this allows multi-word selections in select
			select appname in "$@"
			do
				osascript -e "ignoring application responses" -e "tell application \"$appname\" to quit with saving" -e "end ignoring"
				break
			done
		fi
			
	elif [[ -z $appname ]] # found no matching application
	then
		echo "No running application matches \"$arg\""
	
	elif [[ $appname == *", "* ]] # found >1 matching applications.
	then
		appname=`echo $appname | sed -e 's/^/\"/' -e 's/$/\"/' -e 's/, /\" \"/'`
		echo "\"$arg\" matches multiple applications."
		echo "Choose the application to quit:"
		
		eval set $appname # this allows multi-word selections in select
		select appname in "$@"
		do
			osascript -e "ignoring application responses" -e "tell application \"$appname\" to quit with saving" -e "end ignoring"
			break
		done
	fi
done
To use the above code, you'll need to copy, paste, save, and make executable ... or you can simply grab quit from MacUpdate and drag it to its final destination.

This script essentially executes the osascript command, but without requiring all the verbiage. Originally I tried just putting a one line addition into ~/.profile to do this, but ran into some limitations that made a full-fledged bash script more appropriate.

This script takes one or more application names as arguments, and uses osascript to tell each one to quit. The arguments are not case sensitive, so quit textedit will work. App names with spaces should be quoted or have the spaces escaped. If more than one matching application is found, the user will be prompted to select the correct one from a list. This prompt always occurs with the -p option (see below). The program has two options:

[robg adds: This is a more-thorough and enhanced alternative to a simple script that was posted here years ago. Note that this version only works in 10.5, and the MacUpdate link will always have the newest code, so that's your best bet. I mirrored the code here just in case the original ever vanishes.]

Comments (11)


Mac OS X Hints
http://hints.macworld.com/article.php?story=20090815113819149