Exiting Scripts

I have a script I am writing that is called from filemaker pro. During the running of the script, I would like to put in a way to exit the script if an error occurs. I cannot find an “exit script” command so would I just make the script into a handler and then have error conditionally tied to a “return” statement?

error number -128

… will stop a script from executing any further. It’s the same “User canceled” error that’s generated by the “Cancel” button in most dialogs.

How does one trap & exit a script from within a nested handler? The “Return” or “error number -128” only exits the handler, and the (explicit or implicit) main run handler continues unwanted. (I don’t want a quit/return because I might be running it in a script editor, or in an applet I may want to stop the main run handler and go straight to an idle handler. Here’s where I wish there were a GoTo command in AppleScript!)

Operating System: Mac OS X (10.6.8)

error number -128 stops the execution of the script. However, since it’s an error, it’ll be caught if it’s in a ‘try’ statement or in a handler governed by one:

on run
	try
		stopTheScript()
	end try
	say "I didn't stop."
end run

on stopTheScript()
	error number -128
end stopTheScript

In this case, you need an ‘on error’ section to look out for and pass this particular error:

on run
	try
		stopTheScript()
	on error number n
		if (n is -128) then error number -128
	end try
	say "I didn't stop."
end run

on stopTheScript()
	error number -128
end stopTheScript

Or, if you don’t need to do anything special with any other errors:

on run
	try
		stopTheScript()
	on error number -128
		error number -128
	end try
	say "I didn't stop."
end run

on stopTheScript()
	error number -128
end stopTheScript

^ Thanks, Nigel, for the instructive, detailed reply, above & beyond the call of duty! Thank you for your time!

I’m now straightened-out that error number -128 is “stronger” than resume. That would handle running things within a script editor, but I guess now what I’m really asking is whether there’s a direct command or error handling routine to send the main run handler to the end of the script, skipping any interim lines, kind of like an EOF end of file, or “EOS end of script,” so to speak, such that, e.g., an applet’s idle handler could commence.

Hi Schmye.

return will take you out of a handler, returning the program flow to just after where the handler was called in the process which called it:

--on run
say "starting"
if (5 > 4) then return
say "continuing the run handler"
--end run

on idle
	say "idling"
	return 5
end idle

An alternative ” and in some philosophies preferable ” way would be to use the geography of the return condition:

--on run
say "starting"
if (5 ≤ 4) then
	say "continuing the run handler"
end if
--end run

on idle
	say "idling"
	return 5
end idle

If you want a handler to exit calmly following some error condition, put return in the on error statement:

say "calling the handler"
theHandler()
say "phew!"

on theHandler()
	try
		set fire to trousers
	on error
		say "oops"
		return
	end try
	say "false alarm"
end theHandler

Use user defined handlers then which are called from the Idle handler too.

on run
	try
		-- do something
	on error
		doIdleStuff()
	end try
end run

on idle
	doIdleStuff()
end idle

on doIdleStuff()
	
end doIdleStuff

GoTo commands are considered harmful. Jumping back and forth in code is done using loops, if statements and routines who are all auto-indexed for you. It’s how modern programming languages are designed. Going back to the labeled jumps in code is everything the programming language is designed not to do, therefore it’s evil and should never be used in any circumstances. Besides that programming languages who support GoTo cannot jump from one routine into another, they can only jump within the same routine because it’s how the stack works. So at the end even GoTo wouldn’t help.

Thanks for all the help, guys! … I’m still confused about one thing: I thought that error number -128 stops a script dead in its tracks. I was wanting to use that to make an applet quit upon a certain error in a handler (with a two-line quit/error number -128 combination). So why doesn’t this concept script terminate with the error number -128 in the second_order_nested_handler() ? Why does it work itself back up to the first_order_handler(), and only terminates there? (You never hear the beep in the run handler, so why does the error number -128 work in the 1st handler, but not in the 2nd nested handler? ” they seem otherwise identical. See why I’m confused?)


on run
	first_order_handler()
	beep
end run


on first_order_handler()
	try
		second_order_nested_handler()
		
	on error errorMessage number errorNumber
		display dialog "Error " & errorNumber & return & return & ¬
			errorMessage buttons {"OK"} default button 1 ¬
			with title "(1st order handler)" with icon caution
		error number -128
	end try
end first_order_handler


on second_order_nested_handler()
	try
		error number -128
		
	on error errorMessage number errorNumber
		display dialog "Error " & errorNumber & return & return & ¬
			errorMessage buttons {"OK"} default button 1 ¬
			with title "(2nd order nested handler)" with icon caution
		error number -128
	end try
end second_order_nested_handler

AppleScript: 2.1.2
Operating System: Mac OS X (10.6.8)

Hi Schmye.

It’s similar to the scripts in post #4 above. The call to the second-order handler’s in the ‘try’ section of the first-order handler, so the error generated after the second-order dialog is caught by the first-order ‘try’. This prevents the error from stopping the script and causes the first-order ‘on error’ to be executed, which displays its own dialog and then generates its own error number -128. There’s no ‘try’ block “outside” the first-order one, so this last error succeeds in stopping the script.

As Nigel explained clearly how it works you might be interested in the technical part of it. error is not a termination but an exception like in other programming languages. error is actually another type of AppleEvent, like the run handler you can call error and define an error AppleEvent handler but it looks different in syntax and is called under different conditions. Like all handlers you can call other handlers within an handler, so stack order applies for an try-catch in AppleScript too.

I see. Finally I get it now. Thanks for your guys’ patience with getting the concept through my thick skull!

Well, now that Error number -128 isn’t any good to me in these circumstances, unless someone can come up with a more direct way of quitting an applet in a nested try situation (I now see that it wasn’t nested handlers per se), I can’t think of anything else within an error trap using AppleScript-proper than this vulgar, inelegant workaround:


try
	-- 
	-- 	
on error
	--
	try
		-- 
		-- [Some error condition requiring quitting this applet]
	on error
		-- [Error number -128 here would undesirably get trapped by the first try's error trap]
		tell application "System Events" to do shell script "kill -9 " & ¬
			unix id of process (short name of (info for (path to me))) & ¬
			" &> /dev/null &"
	end try
end try

But this only extricates me in an applet; I still don’t know how to get out (just stop the script) when running in a script editor. (Which is more evil?”GoTo statements, or my extreme-prejudice workaround? :wink: )

sigh. I just wish that “quit” meant quit… Oh, I suppose “quitting” the script as separate from quitting the applet somehow makes sense in the world of object-oriented programming (I guess the applet is some kind of “parent” to the script “child”), but to this GoTo-loving AppleScripter whose only prior programming experience was some 40 years ago with procedural languages such as BASIC, FØRTRAN, and ALGOL (hey, DEC Extended Compiled BASIC had optional line numbers that were mostly used as labels for GoTo commands :stuck_out_tongue: ), this annoying use of “quit” certainly isn’t intuitive, but no doubt there’s some scenario where it’s advantageous. I confess I approach AppleScript still in a procedural programming manner. Why can’t there be a no-recourse quit or halt or “EOF” (end of script) for the script itself?

One method for dealing with stopping execution in arbitrarily deeply nested “try”'s:


set giveUp to false

try
	set someVariable to "one" as integer
on error
	try
		set anotherVariable to "two" as integer
	on error
		set giveUp to true
	end try
end try

if giveUp is true then return
display dialog "I'm still executing!"

^ Yeah, thanks, I can see how that would work… But by working it’s way back up through all the nested tries and resuming where the blocks left off after an error occurred down the line, it could do things undesirable in them (the reason I brought this up in the first place), especially when the nested tries are occurring due to nested handlers ” unless I put those “if giveUp is true.” checks strategically spread out all over my script. What a PITA to have to formulate elaborate workarounds crapping-up a script, for a language evidently without a simple “stop” command! (unless I’m missing something)

I just tried a script with one line: stop, and it gives a Result of “stop,” but it apparently doesn’t do anything, because if I add another line, it executes it.

It’s just a matter of design. Either don’t nest ‘try’ statements, or don’t exit scripts from within nested ‘try’ statements, or, if you do, make sure that the “User canceled.” error gets passed back up the line:


on run
	first_order_handler()
	beep
end run


on first_order_handler()
	try
		second_order_nested_handler()
		
	on error errorMessage number errorNumber
		if (errorNumber is -128) then
			error number -128
		else
			display dialog "Error " & errorNumber & return & return & ¬
				errorMessage buttons {"OK"} default button 1 ¬
				with title "(1st order handler)" with icon caution
		end if
	end try
end first_order_handler


on second_order_nested_handler()
	try
		error number -128
		
	on error errorMessage number errorNumber
		if (errorNumber is -128) then
			error number -128
		else
			display dialog "Error " & errorNumber & return & return & ¬
				errorMessage buttons {"OK"} default button 1 ¬
				with title "(2nd order nested handler)" with icon caution
		end if
	end try
end second_order_nested_handler

Or maybe:


on run
	first_order_handler()
	beep
end run


on first_order_handler()
	try
		second_order_nested_handler()
		
	on error errorMessage number errorNumber
		handleError(errorMessage, errorNumber)
	end try
end first_order_handler


on second_order_nested_handler()
	try
		error number -128
		
	on error errorMessage number errorNumber
		handleError(errorMessage, errorNumber)
	end try
end second_order_nested_handler

on handleError(errorMessage, errorNumber)
	if (errorNumber is -128) then
		error number -128
	else
		display dialog "Error " & errorNumber & return & return & ¬
			errorMessage buttons {"OK"} default button 1 ¬
			with title "(2nd order nested handler)" with icon caution
	end if
end handleError

I don’t think this is as complicated as you’re currently thinking it is.

I think error -128 stops a script in all instances except for being inside a “try” statement, and the only purpose of a “try” statement is to catch potential errors and maintain the script’s ability to execute regardless of what happens inside… so it’s natural that it keeps going when there’s an error… that is the sole point of the construct.

It would probably help us out to see your actual code.

“Error -128” stops a script dead even if it’s called inside a handler:

doSomething()
display dialog "I'm still executing!"

on doSomething()
	error number -128
end doSomething

It just doesn’t stop a script dead when called inside a “try.” Because the whole purpose of the “try” is to keep the script going.

And even your more recent example doesn’t actually require my method, you can go as deep as you want in “try’s” and whenever you get to the bottom, you can still stop the whole thing with an “error number -128” as long as the first condition of the “try” on each step failed so the script got there, and you want any other code within the “on error” handlers to execute.

try
	set someVariable to "one" as integer
on error
	try
		set anotherVariable to "two" as integer
	on error
		error number -128
	end try
end try

display dialog "I'm still executing!"

Just out of curiosity, out of BASIC, FØRTRAN, and ALGOL, did any of them actually have error handling?

I’m not saying you don’t have a good usage case for this situation you’ve presented, but I’m having trouble picturing it… if you want the script to stop for certain when certain criteria are met, why is that taking place as the error handler on a “try?” Why not just use an “if” statement? And why all functions calling functions within error handlers? The people reading this have collectively got 100’s of thousands if not millions of lines of AS behind them, but from my experience, I’m guessing we haven’t had problems with these particular issues. Don’t get me wrong, Applescript’s got its confusing parts to wrestle with. Just having the stop command be “error number -128” is pretty non-intuitive. But if you’re used to much older languages, I think your best bet would be to give us a bigger chunk of your intended program logic and let us tell you how we’d structure that in AS. My hunch is that it’s not going to be that bad.

It’s hard to say without knowing the actual goal.

Yeah, I think the key, as Nigel said, is to make sure that the “User canceled” error gets passed back up the line. That’s the least “urban sprawl” approach, and I can make that work.

Oh, I didn’t answer your questions: Yes, all three procedural programming languages had robust error handling. And my usage case was a handler that called the macOS Keychain for a password, and if it errored-out from the password not already being there, it would call another, nested handler that gives the user the ability to add the password to Keychain, then return to the first handler, which would then recursively call itself to fetch the password again. My problem was, both handlers have their own dialogs for errors such as wrong password, canceling the dialog, etc., and the passing back up the chain a “User canceled” error would lead to double error messages and resumption of the first handler, when a certain dialog in the nested handler had said it would now just quit the applet. Now that I know to be sure to pass the error back up the line, that should fix it if I can get it all sorted out in my mind ” or I can just stick with my “terminate with extreme prejudice” workaround, because I was quitting the applet at that point, anyway, which I’ve already gotten to work.

Thanks again for both of you all’s help! (That’s southern for, “Thanks for both of your help.” :cool: )

In case it’s helpful to you, I’d do something like this.

I don’t have any “try” statements in here. Not that a “Try” statement is something to be avoided, I just didn’t need any in this situation.

Of course my password “validation” is a total joke, but I saw no reason to bother with any serious logic for that since this was just about program flow.

IMHO, if you want password validation, then this would be a great opportunity to use ASObjC so you can check the password and provide feedback live inside a dialog, instead of in a repeat loop.

Oh, and obviously, if anyone’s using “!!PasswordNotFound!!” for their password, then this script will perform very poorly.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions


set passwordName to "Jim"
set retreivedPassword to getPassword(passwordName)


on getPassword(passwordName)
	set thePassword to "!!PasswordNotFound!!"
	repeat
		set thePassword to do shell script "security find-generic-password -wl '" & passwordName & "' || echo '!!PasswordNotFound!!'"
		if thePassword ≠ "!!PasswordNotFound!!" then
			exit repeat
		else
			my setPassword(passwordName)
		end if
	end repeat
	return thePassword
end getPassword

on setPassword(passwordName)
	set newPassword to ""
	set repeatCount to 0
	repeat
		set checkResult to my passwordCheck(newPassword)
		if checkResult is true then
			exit repeat
		else
			if repeatCount > 0 then
				set initialText to "The last password you entered only had " & checkResult & " characters, but 6 is the minimum." & return
			else
				set initialText to "No password was found in your keychain for \"" & passwordName & "\"" & return
			end if
			set newPassword to the text returned of (display dialog initialText & "Please enter a new password with 6 characters or more, and the entry will be saved in your keychain." default answer "" buttons {"Cancel", "OK"} default button "OK")
			set repeatCount to repeatCount + 1
		end if
	end repeat
	do shell script "security add-generic-password -a " & quoted form of passwordName & " -w " & quoted form of newPassword & " -s " & quoted form of passwordName
end setPassword

on passwordCheck(aPassword)
	set charCount to (count of characters in aPassword)
	if charCount < 6 then
		return charCount
	else
		return true
	end if
end passwordCheck

I think initially Schmye was looking for an exit, syscall or signal command, I think that was his goal. Because in AppleScript everything is an AppleEvent, including quit and error handlers, there is no such similar command. Therefore an error is not actually an error but an AppleEvent like any other, only of an certain type. Quitting is not exiting but an system wide used AppleEvent to call another function to eventually gently stops itself (as all applications does when pressing cmd+q or choose quit from the menu).

If you look at AppleScript as an RPC-language it makes it all very clear why it behaves this way and why it behaves so different from all other programming languages. As a rule of thumb, when you’re in AppleScript you have to forget every other programming language that exists because it will not clarify anything but leaves you confused most of the time.

^ Yeah, DJ Bazzie Wazzie, that’s what I was trying to say earlier: As someone who’s never programmed in a modern object-oriented language, I freely admit that by using AppleScript, old habits die hard, and I’m often trying to pound a procedural square peg through an object-oriented round hole. :smiley:

And thanks, t.spoon, for the no-try routine that avoids having to follow errors up the line of nested handlers.