Detecting contents of Terminal line -commands complete?

Hey all,

Im writing a gui applescript for a client that runs some commands in Terminal. It is an ssh connection, and when its done it will logout, leaving Terminal with the default prompt (Computer name:~ user$). The problem Im running into is detecting when its finished so I can quit Terminal. I tried the “if window 1 is busy” approach at first, but for some reason since it is an ssh connection, the script always finds the window to not be busy, so Im looking at something like this:

tell application “Terminal”
activate
repeat
if contents of last line of window 1 contains “$” then
quit
exit repeat
else
delay 3
end if
end repeat
end tell

The “of last line” is the problem. If I take that out and just scan the entire window, it will work, but Id like to only check to see if the last line has the correct prompt. Or, ideally, Id like an entirely better way to see if the commands have completed and Terminal is sitting idly at the user prompt.

Thanks for any help!

When an ssh connection happens, a process called “ssh” runs on the computer. You can see it using “Activity Monitor”. As such you can check if that process is running. So for your script which runs its commands over an ssh connection you can detect if the ssh process is running. If it is then your script is still doing something and if ssh isn’t running then your script is done, at which time you can quit the Terminal. Here’s a script to check if the ssh process is running…

try
	do shell script "ps | grep ssh"
	set ssh_is_running to true
on error
	set ssh_is_running to false
end try
ssh_is_running

This script will quit the Terminal if the ssh process isn’t running…

try
	do shell script "ps | grep ssh"
on error
	tell application "Terminal" to quit
end try

To avoid the Terminal window hanging around, try something like this:

tell application "Terminal"
	do script "ssh YOUR PARAMS HERE;exit" in window 1
end tell

or this:

tell application "Terminal"
	do script "exec ssh YOUR PARAMS HERE" in window 1
end tell

The “;exit” tells the shell to exit after the preceding command has finished.

The exec basically tells the shell to make the execd command its last command. In technical terms, the shell skips the fork of the standard fork+exec process launching system call duo. The execd process replaces the shell process. It inherits the shells PID. So anything waiting for the original shell process to exit will end up waiting for the shh process to exit instead (because the shell process was wholly replaced by the ssh process).

For this to be an appropriate technique, you will have to be able to decide that shh (or other command line program) is the last command you want to run in that window before it is time to start that command. If you are only ever running that one ssh command, then this condition has already been met. Something that would be more problematic would be a situation where you can only decide whether you need to run another command in that Terminal window based on the output of the current command.

If you are using “compound” shell commands (multiple commands indicated by parenthesis, curly braces, semicolons, pipes (vertical bars), double ampersands, loops or conditionals, etc.) in your do script command to Terminal, then you cannot put exec directly in front of it. Instead you would need to put the exec directly in front of only the last command to be run. Exactly how to do this depends on which type of compound command you are using. In this situation, it would probably be better just to append “;exit”, like in the first method.

The main difference between the two techniques is that with exit, the shell process hangs around until after the shh process has exited when it then processes the exit command and exits itself. For exec the shell process is replaced with the ssh process. The exec-based replacement is a bit more efficient, but harder to use correctly. The efficiency gains are probably not enough to worry around.

Terminal “busy” adjustment

It appears that part of the determination of the value of the busy property of a Terminal window is based on the settings in the “Processes” section of the Terminal Inspector window (Command-I or File > Show Info). ssh is in the default list of processes that are checked when closing a Terminal window. When I took ssh out of the list, in addition to enabling the prompt when closing the window, it also made the busy property return true while ssh was running. The change in the list of processes seems to have an effect on the busy status even if the prompt-before-closing setting is configured to ignore the list (always prompt or never prompt).

Model: iBook G4 933
AppleScript: 1.10.7
Browser: Safari 3.0.4 (523.12)
Operating System: Mac OS X (10.4)

Thanks for the replies.

I like your idea regulus. I ended up just checking the name of the window for bash (if name of window 1 contains “bash” then) to see if it was logged out. Its essentially checking for the same thing, and your code is a bit neater, but the name check suited my script since I’m concerned the user could interfere with the script by either closing the window or opening a new window at an inopportune time. I hid the Terminal, but Im trying to take into account all the ways this script could fail and run error checks.

Thanks for explaining why it wasn’t finding it to be busy Chrys. As for the exit and all the other stuff…what I need it to do after the commands are run:

Check it has completed and exited successfully (completed check still not added to script below). Quit Terminal. Display a message to the user that the script has completed.

As far as I know, none of these can be accomplished by adding unix commands to the commands I’ve issued in Terminal So if thats the case, I need to know when my commands have completed so I can continue applescripting the remainder of the script. Ive worked it all out though. Probably not as efficient as it could be, but it works. Still missing a few little things, but this is the basic idea, I welcome any suggestions for improving it:



--define handlers for window error checks

on winCheck()
	tell application "System Events"
		if not (exists process "Terminal") then
			tell application "sshopen"
				activate
				display dialog "Terminal is not running. The script will cancel. Try running it again" buttons {"OK"} default button "OK"
			end tell
			keystroke "." using command down
		else
			tell application "Terminal"
				if (count of windows) is not 1 then
					tell application "sshopen"
						activate
						display dialog "There are an incorrect amount of Terminal windows open. This would likely cause the scripted actions to fail. The script will cancel, try running it again" buttons {"OK"} default button "OK"
					end tell
					do shell script "killall Terminal"
					tell application "System Events" to keystroke "." using command down
				end if
			end tell
		end if
	end tell
end winCheck

on winCheckwOpts()
	tell application "System Events"
		if not (exists process "Terminal") then
			tell application "sshopen"
				activate
				display dialog "Terminal is not running. The script will cancel. Check to see if any files were moved and try running it again" buttons {"OK"} default button "OK"
			end tell
			keystroke "." using command down
		else
			tell application "Terminal"
				if (count of windows) is not 1 then
					tell application "sshopen"
						activate
						display dialog "There may have been a problem. Check to see if your files were moved. If they were not moved, quit Terminal and try running this script again" buttons {"Stop script without quitting Terminal", "Quit Terminal"} default button 1
						if button returned of result is "Quit Terminal" then
							tell application "Terminal"
								activate
								quit
							end tell
						end if
					end tell
					tell application "System Events" to keystroke "." using command down
				end if
			end tell
		end if
	end tell
end winCheckwOpts



--Start script - if Terminal is open, quit it and start in new window

tell application "System Events"
	try
		
		if exists process "Terminal" then
			tell application "sshopen"
				activate
				display dialog "You must quit Terminal for this script to work. Press 'OK' to quit Terminal and continue with the script or press 'Cancel' to stop the script" buttons {"Cancel", "OK"} default button "Cancel"
				if button returned of result is "OK" then
					tell application "Terminal"
						activate
						quit
					end tell
				end if
			end tell
		end if
		
		--explanation to user
		
		tell application "sshopen"
			activate
			display dialog "This script uses the application 'Terminal' to move your files from the 'FilesToBeMoved' folder to the 'FromThisFolder' folder. 
			
While the script is running, please do not activate Terminal or alter any Terminal windows. It will run in the background and you can continue to work in other applications. You will see a message letting you know when the script is finished."
		end tell
		
		--ssh into server
		
		tell application "Terminal"
			launch
			set the number of rows of window 1 to 1
			do script "ssh -l root <server-address>" in window 1
			tell application "System Events"
				set visible of process "Terminal" to false
			end tell
			delay 6
			
			--window error check
			
			my winCheck()
			
			--enter password
			
			do script "<password>" in window 1
			delay 5
			
			--window error check, and make sure login was successfull
			
			my winCheck()
			
			if contents of window 1 does not contain "root#" then
				tell application "sshopen"
					activate
					display dialog "Terminal was not able to log in to the server. Either the password has changed or another error occured. The script will cancel, try running it again" buttons {"OK"} default button 1
					do shell script "killall Terminal"
				end tell
			end if
			
			--enter move commands
			
			do script "<list-of-commands>; logout" in window 1
			
			--after important commands, window error check with options
			
			my winCheckwOpts()
			
			--end script by checking to see if ssh is logged out
			
			repeat
				my winCheckwOpts()
				if (get name of window 1) contains "bash" then
					quit
					tell application "sshopen"
						activate
						display dialog "The file mover script has finished running. Please check to make sure your files were moved successfully." buttons {"OK"} default button 1
					end tell
					exit repeat
				else
					delay 3
				end if
			end repeat
		end tell
		
		--if Terminal is still active there is a problem
		
		if exists process "Terminal" then
			tell application "sshopen"
				activate
				display dialog "Terminal is still active indicating there may have been a problem. Check to see if your files were moved. If they were not moved, try running this script again" buttons {"Stop script without quitting Terminal", "Quit Terminal"} default button 1
				if button returned of result is "Quit Terminal" then
					tell application "Terminal"
						activate
						quit
					end tell
				end if
			end tell
		end if
		
	on error
		tell application "sshopen"
			activate
			display dialog "There was an error. The script was cancelled"
		end tell
		
	end try
end tell


That makes sense if you’re worried about the user messing with Terminal’s windows.

One other thought…
It seems from your script that its main purpose is to transfer files. If that’s all you’re doing then have you tried rsync? With rsync you don’t have to use a terminal window at all, and it uses a secure ssh connection to do the transfers.

The thing is Im sshing into an IRIX system and running scripts that reside on the server that contain proprietary commands to move files (keeping all properties) and do a few other things. Need to ssh in and run those scripts.

Id love to be able to script this ssh connection without using Terminal or the gui, but the password interaction seems unscriptable…unless you know a way? :slight_smile:

Use a Public/Private key:
http://www.astro.caltech.edu/~mbonati/WIRC/manual/DATARED/setting_up_no-password_ssh.html

Cheers.

Niiiice! just tried that out, worked great. Now I can run a shell script in the background and not worry about the user interrupting the file transfer.

Thanks waltr

Thanks to everyone who helped with this