How to wait for osascript to produce a result - bash

sometimes my AppleScript code seems to fail, on other machines is works.
I run this AppleScript code as heredoc from inside a shell script. The shell script is a postinstall script run by a pkg installer and runs as root:
#!/bin/sh
set -x
logfile="/Library/Logs/EZEEP Connector Installer.log"
LogMessage()
{
echo $(date) $1 >> "${logfile}"
}
LogMessage "................................."
LogMessage "Installer postinstall started ..."
LogMessage "................................."
# set file system tag 'Printing' on app bundle:
/usr/bin/osascript <<'EOD'
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
on addTags:tagList forPath:posixPath -- add to existing tags
set aURL to current application's |NSURL|'s fileURLWithPath:posixPath -- make URL
#display dialog aURL as string
-- get existing tags
set {theResult, theTags} to aURL's getResourceValue:(reference) forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
if theTags is not missing value then -- add new tags
set tagList to (theTags as list) & tagList
set tagList to (current application's NSOrderedSet's orderedSetWithArray:tagList)'s allObjects() -- delete any duplicates
end if
aURL's setResourceValue:tagList forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
end addTags:forPath:
tell application "Finder"
set thePath to (POSIX path of (application file id "com.thinprint.ezeep.Connector" as alias))
my addTags:{"Printing"} forPath:thePath
end tell
EOD
#sleep 10
sudo -u ${USER} /usr/bin/osascript -e 'tell application "/Applications/ezeep Connector.app" to launch'
LogMessage "................................."
LogMessage " Installer postinstall finished ."
LogMessage "................................."
Sometimes the targeted app bundle gets a tag, sometimes not. If I uncomment the last sleep in my code sample, then everything works as expected.
Even on 2 different VMs we have the tag always created on one VM and failing to create on the other.
Is there a proper way to wait for the result of the add tag operation and then proceed with the script? Or is the sleep 10 command a reliable solution to have it run on dozens or event hundreds of Macs without failing on even a few?
kind regards,
Robert

First, it's always good idea to avoid scripting the Finder (a busy app that can produce odd delays and errors in scripts). Since you're using AppleScriptObjC anyway, you can get the URL you need from NSWorkspace. Then all you need to do is add a repeat/check/delay loop that looks to see if the tag has been added.
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
set theURL to my findAppURLFromID:"com.thinprint.ezeep.Connector"
my addTags:{"Printing"} forURL:theURL
my checkTag:"Printing" forURL:theURL
(* get app package URL from NSWorkspace *)
on findAppURLFromID:appID
set sharedWorkspace to (current application's NSWorkspace's sharedWorkspace)
set appURL to sharedWorkspace's URLForApplicationWithBundleIdentifier:appID
return appURL
end findAppURLFromID:
(* altered to accept URL directly *)
on addTags:tagList forURL:aURL
set {theResult, theTags} to aURL's getResourceValue:(reference) forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
if theTags is not missing value then
set tagList to (theTags as list) & tagList
end if
(*
I moved the following statement out of the 'if' block. This would not be
called if the package file has no tags, so the script would feed an unaltered
AppleScript list to 'setResourceValue:forKey:error'. That may work, dunno
but better to put everything into the correct object format
*)
set tagList to (current application's NSOrderedSet's orderedSetWithArray:tagList)'s allObjects() -- delete any duplicates
aURL's setResourceValue:tagList forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
end addTags:forURL:
(* checks every half second to see if the list of tags contains "Printing" *)
on checkTag:tagName forURL:aURL
repeat 120 times
set {theResult, theTags} to aURL's getResourceValue:(reference) forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
set tagList to (theTags as list)
if tagList contains "Printing" then return
delay 0.5
end repeat
display alert "tagging timed out after 1 minute"
end checkTag:forURL:
I fixed one (potential) error in your script; see the text comment inline.
This assumes that you are only adding a single tag. If you want to add (and check) for multiple tags, you'll need to complexity checkTag:forURL. Also, I haven't done much with error handling (any case where for some reason adding the tag fails) except toss up an alert. I used a contingent repeat loop to keep it from going on infinitely on failure, but you may want it to fail silently, or do something else at that point.

Related

Reference external script file

I'd like to implement this script (also listed below) as a separate file. How do I formulate the reference to it as a POSIX path?
tell application "ASObjC Runner"
activate
set chooserResult to run the script {chooseFilesOrFolders} with response
-- the above line would have to reference something like RemoteVolume/test.scpt
end tell
The referenced script itself existing as separate file "test.scpt":
script chooseFilesOrFolders
tell current application's NSOpenPanel's openPanel()
setTitle_("Choose Files or Folders") -- window title, default is "Open"
setPrompt_("Choose") -- button name, default is "Open"
setCanChooseFiles_(true)
setCanChooseDirectories_(true)
setAllowsMultipleSelection_(true) -- remove if you only want a single file/folder
get its runModal() as integer -- show the panel
if result is current application's NSFileHandlingPanelCancelButton then error number -128 -- cancelled
return URLs() as list
end tell
end script
You must use the fFinder path for your script test.scpt, and then call the library using "load script file" command:
In your test.scpt file :
On ChooseFilesOrFolders
-- all your script lines here
return URLs() as list -- to send back result of your sub routine
end ChooseFileOrFolders
In your main script :
Set Script_Lib to "HD:Users:me:Desktop:test.scpt" -- the complete path to your text script.
Set My_Lib to (load script file Script_Lib)
-- insert here you main script lines, and when you want to call the function :
tell My_Lib to Set chooserResult to ChooseFilesOrFolders
-- Here, chooserResult will contain the list returned from test script
Also note that your script "test can also contains many other subroutines which can be called as fonctions in your main script.
I hope it helps.

Embed a bash shell script in an AppleScriptObjC application with Xcode

I have attempted to follow the instructions on this post but I am falling short of understanding how some of the posters instructions work.
I want to be able to package the app with a prewritten bash script and then execute it, but don't follow from Step 4 onwards.
Post writes:
4. Also in your AppleScriptObjC script, add the following where appropriate:
property pathToResources : "NSString" -- works if added before script command
5. Where appropriate, also add the following in your AppleScriptObjC script:
set yourScript to pathToResources & "/yourScriptFile.sh"
-- gives the complete unix path
-- if needed, you can convert this to the Apple style path:
set yourScriptPath to (((yourScript as text) as POSIX file) as alias)`
6. As an aside, you could then open the file for read using
tell application "Finder"
open yourScriptPath
end tell
Questions:
Where do I add the line:
property pathToResources : "NSString"
Do I add which of the following, and where?
set yourScript to pathToResources & "/yourScriptFile.sh"
OR
set yourScriptPath to (((yourScript as text) as POSIX file) as alias)
How is it possible to execute the script itself? The mention As an aside, you could then open the file for read using only covers the Apple style path, it does not cover using the aforementioned style.
Can anyone shed a bit more light on this for me, or post a static copy of a AppDelegate.applescript file that shows how the original poster required the base code to be used? I have tried his method and looked across the internet for the past 3 weeks to no avail. I don't want to have to convert all my code for specific tools from bash scripts into AppleScript, as this would take a lot of work.
I only need to know how to reference to the script file (for example myBashScript.sh) in my app, which would reside in the application and be included by Xcode at time of compilation.
I think you should use the command path to resource <specifiedResource>.
See Standard Additions, path to resource.
You could set it by set myVariableName to path to resource "myBashScript.sh" or just use the command instead of your property so it points always to the right place (a user could move your app while running... lol).
ADDITION:
I did it that way in my AppleScript-Application:
on run_scriptfile(this_scriptfile)
try
set the script_file to path to resource this_scriptfile
return (run script script_file)
end try
return false
end run_scriptfile
Whenever I want to run a script that is bundled within my app I do this:
if my run_scriptfile("TestScript.scpt") is false then error number -128
run_scriptfile(this_scriptfile) returns true when everything worked.
I ended up bringing all the information together and now have a solution.
This takes into consideration the following facts:
firstScript = variable name that points to a script called scriptNumberOne.sh
scriptNumberOne.sh = the script that I have embedded into my application to run
ButtonHandlerRunScript_ = the name of the Received Action in Xcode
pathToResources = variable that points to the internal Resources folder of my application, regardless of it's current location
Using this information, below is a copy of a vanilla AppDelegate.applescript in my AppleScriptObjC Xcode project:
script AppDelegate
property parent : class "NSObject"
property pathToResources : "NSString"
on applicationWillFinishLaunching_(aNotification)
set pathToResources to (current application's class "NSBundle"'s mainBundle()'s resourcePath()) as string
end applicationWillFinishLaunching_
on ButtonHandlerRunScript_(sender)
set firstScript to pathToResources & "/scriptNumberOne.sh"
do shell script firstScript
end ButtonHandlerRunScript_
on applicationShouldTerminate_(sender)
-- Insert code here to do any housekeeping before your application quits
return current application's NSTerminateNow
end applicationShouldTerminate_
end script

OSX: How can check whether a file exists in current directory using applescript?

I want to make an automator app which creates an empty file in current directory.
I did some google search and found:
http://hints.macworld.com/article.php?story=20050219134457298 and http://hints.macworld.com/article.php?story=20100509134904820
However, I want to do something more powerful.
If the specified file already exists, I want to show a warning instead of overwriting the original file, which is what one of the above link does. (The other one creates a text file using textEdit. I do not want to create text file. I want an empty file like what linux/unix does)
I already figured out how to do most of the part, but
How can check whether a file exists in current directory using applescript??
How can I concatenate two variable in applescript?
Checking if a file exists (assuming thefullpath is already set as in the referenced question):
tell application "Finder"
if exists POSIX file thefullpath then
--do something here like
display alert "Warning: the file already exists"
end if
end tell
Not sure what you mean by the second part but if you want to concatenate strings stored in var1 and var2 you could simply do
var1 & var2
Something I have been using a lot of late for this sort of thing is the command /bin/test
The test test for the existence of in this case a file
if (do shell script "/bin/test -e " & quoted form of (POSIX path of theFile) & " ; echo $?") is "1" then
-- 1 is false
--do something
end if
The -e option:
-e file True if file exists (regardless of type).
The are tons of other test options shown in the /bin/test man page
The following code, adapted from your second link, is usually right, but it doesn't always work. The current directory is better specified as the directory of the document that is being opened which is most likely from the Finder's front window, but not necessarily. I like to write code that will work no matter what.
on run {input, parameters}
tell application "Finder"
set currentPath to insertion location as text
set x to POSIX path of currentPath
display dialog "currentPath: " & (x as text)
end tell
return x
end run
I wrote a whole "Run AppleScript" action to put things into context:
on run {input, parameters}
# count the number of files
set numFiles to 0
repeat with f in input
# warn the user that folders are not processed in this app
tell application "Finder"
if (kind of f is "Folder") then
display dialog "The item: " & (f as text) & " is a folder. Only files are allowed. Do you want to continue processing files or do you want to cancel?"
else
set numFiles to numFiles + 1
end if
end tell
end repeat
# require that at least one file is being opened
if numFiles < 1 then
display alert "Error: the application Test1.app cannot be run because it requires at least one file as input"
error number -128
end if
# get the current directory from the first file
set theFirstFile to (item 1 of input)
tell application "System Events" to set theFolder to (container of theFirstFile)
# ask the user for a file name
set thefilename to text returned of (display dialog "Create file named:" default answer "filename")
# create the file
tell application "System Events" to set thefullpath to (POSIX path of theFolder) & "/" & thefilename
set theCommand to "touch \"" & thefullpath & "\""
do shell script theCommand
# return the input as the output
return input
end run
The "touch" command is OK. If the file doesn't exist, it is created and if it does exist, only the modification date is changed (which isn't too bad) but it doesn't overwrite the file. If your file is being overwritten, it's not the touch command that is doing it.
I changed the default file name to remove the extension ".txt" This extension may default to being opened by TextEdit.app, but you can change this in the Finder by choosing "Get Info" for a file and changing the "Open With" property. You can change which application opens the file with that extension or you can change them all. For example, all of my ".txt" files are opened with BBEdit.app
Will you vote my answer up?
Another option that doesn't require Finder or System Events is to try to coerce a POSIX file or file object to an alias:
try
POSIX file "/tmp/test" as alias
true
on error
false
end try

Print to stdout from osascript/Applescript

I have some AppleScript code that I'm executing with osascript. This is part of a larger Perl program. I'd like to be able to print to stdout from the AppleScript, then have the Perl script process the output. But I haven't been able to print from within AppleScript. What should I do?
Here's what I've tried:
do shell script "echo Foo". Does not ouptut Foo.
This Google Groups discussion does some trickery to open /dev/fd/1. For me, I get an error of "File Macintosh HD:dev:fd:1 wasn't found"
Here's the script I'm running:
tell application "Safari"
set window_list to every window
repeat with the_window in window_list
set tab_list to every tab in the_window
repeat with the_tab in tab_list
set the_url to the URL of the_tab
-- I'd like to put a print statement here,
-- instead of display dialog
display dialog the_url
end repeat
end repeat
end tell
Since osascript will automatically print the last value of a program, I could collect the URLs into a list and print that. But then my Perl script would have to parse the list, remove quotes, etc. It seems like it should be more straightforward to just print one URL per line.
Thanks
I don't know how to do what you're asking and I don't know Perl, however I think you could make the parsing from perl simple if you collect your urls in a string instead of a list. Each url would be on a separate line of the string. Perl should be able to turn that into an array pretty easily and then do something with it. Something like the below applescript. Of course you can use a different separator in the applescript. I used "return" but it could just as easily be a "comma" or any other character you want. Whatever is easiest for you in perl to change the string to an array.
set urlString to ""
tell application "Safari"
set window_list to every window
repeat with the_window in window_list
set tab_list to every tab in the_window
repeat with the_tab in tab_list
set the_url to the URL of the_tab
set urlString to urlString & the_url & return
end repeat
end repeat
end tell
return text 1 thru -2 of urlString
I found that I can use 'log' to dump results to STDERR,
though I had to use Chrome instead of Safari:
#!/usr/bin/osascript
tell application "Chrome"
repeat with w in every window
repeat with t in tabs of w
log (get URL of t)
end repeat
end repeat
end tell
Just use log is OK.
MacBookPro:~ zxj5470$ cat demo.scpt
tell application "Terminal"
set WindowNum to get window count
log WindowNum
end tell
MacBookPro:~ zxj5470$ osascript demo.scpt
1

Getting the file name of files dropped on the script

I made this Applescript script to create symbolic links.
Appart from POSIX path of, how can I get the file name, without the path, of the dropped file?
on open filelist
repeat with i in filelist
do shell script "ln -s " & POSIX path of i & " /Users/me/Desktop/symlink"
end repeat
end open
PS: I know this expects many files to be dropped and tries to create many links with the same name, which gives an error. Actually I copied this example from a website and as I don't know almost anything about Applescript, I don't know how to do this for a single file, help on that would be appreciated too.
I'm not sure what precisely you're trying to do, but I have a guess. Is the idea that you want to take every file dropped on the script and create a symbolic link to each one on the Desktop? So if I drop ~/look/at/me and ~/an/example, you'll have ~/Desktop/me and ~/Desktop/example? If that's what you want, then you're in luck: ln -s <file1> <file2> ... <directory> does exactly that. (Edit: Although you have to watch out for the two-argument case.) Thus, your code could look like this:
-- EDITED: Added the conditional setting of `dest` to prevent errors in the
-- two-arguments-to-ln case (see my comment).
on quoted(f)
return quoted form of POSIX path of f
end quoted
on open filelist
if filelist is {} then return
set dest to missing value
if (count of filelist) is 1 then
tell application "System Events" to set n to the name of item 1 of filelist
set dest to (path to desktop as string) & n
else
set dest to path to desktop
end if
set cmd to "ln -s"
repeat with f in filelist & dest
set cmd to cmd & " " & quoted(f)
end repeat
do shell script cmd
end open
Note the use of quoted form of; it wraps its argument in single quotes so executing in in the shell won't do anything funny.
If you want to get at the name of the file for another reason, you don't need to call out to the Finder; you can use System Events instead:
tell application "System Events" to get name of myAlias
will return the name of the file stored in myAlias.
Edit: If you want to do something to a single file, it's pretty easy. Instead of using repeat to iterate over every file, just perform the same action on the first file, accessed by item 1 of theList. So in this case, you might want something like this:
-- EDITED: Fixed the "linking a directory" case (see my comment).
on quoted(f)
return quoted form of POSIX path of f
end quoted
on open filelist
if filelist is {} then return
set f to item 1 of filelist
tell application "System Events" to set n to the name of f
do shell script "ln -s " & ¬
quoted(f) & " " & quoted((path to desktop as string) & n)
end open
It's pretty much the same, but we grab the first item in filelist and ignore the rest. Additionally, at the end, we display a dialog containing the name of the symlink, so the user knows what just happened.
As an example, you can work with the Finder instead of a shell script to get the name of a single file that is dropped on the script that is saved as an application. If you don't need the display dialog, you can remove it, but you have the file name as a variable to work with:
on open the_files
repeat with i from 1 to the count of the_files
tell application "Finder"
set myFileName to name of (item i of the_files)
end tell
display dialog "The file's name is " & myFileName
end repeat
end open

Resources