I'm trying to set a variable using AppleScript from automator:
do shell script "launchctl setenv LASTMAX \"" & today & "\""
I have a variable today that i want to assign to the variable LASTMAX
but when i check the property with echo $LASTMAX i keep getting the initial value,
even after i reboot.
How can i set variables with AppleScript?
Is there a different way to store persistant variables with automator?
Thanks!
You can use script objects to store your data in an out of the way place.
Persistant variable storage in Automator
storing the script seems to only work the first time.
When I paste the script refernced above into an Automator workflow and run the workflow several times, the returned value increments by one each time the script is run. Please post your code.
Related
I am creating a text file and looping through every Safari window, every tab, and pasting each link into the .txt file. However I am getting an extra missing value written to the .txt file. I know I can explicitly check for missing values but I don't understand where is the "empty" window/value?
on run {input, parameters}
(* create text file to hold links *)
tell application "Finder" to make file at desktop with properties {name:"links_0"}
set classicPath to (((path to desktop folder) as string) & "links_0")
(* get links of all windows *)
tell application "Safari"
repeat with this_window in windows
set the_URLs to ""
repeat with this_tab in tabs of this_window
set the_URLs to the_URLs & URL of this_tab & return
end repeat
do shell script "echo " & quoted form of the_URLs & " >> " & POSIX path of (classicPath)
do shell script "echo >> " & POSIX path of (classicPath)
end repeat
end tell
return input
end run
sample output (2 windows, each with 2 tabs):
https://discussions.apple.com/thread/64896
https://discussions.apple.com/thread/22543
https://discussions.apple.com/thread/25140
https://discussions.apple.com/thread/22546
missing value
An easier way to do this would be:
set my text item delimiters to linefeed
set fp to POSIX path of (path to desktop folder) & "links_0.txt"
close access (open for access fp) -- Create the file if it doesn't exist
# set eof of fp to 0 -- Erase any existing contents in the file
tell application id "com.apple.Safari" to tell every tab of every window ¬
to tell (the URL as text) & linefeed to continue write it to fp ¬
starting at eof
This currently appends URLs to the end of the file. To overwrite the file, uncomment this line:
# set eof of fp to 0
by removing the hash symbol.
Note: There's potential for confusion having read the comment left by #user3439894, where he states:
"It also shows code missing from CJK's answer in which a file should be closed after being written to. It also wraps relevant code in a try statement so if there is an error it attempts to close the file."
I would take what you read at the provided link with a pinch of salt. Sadly, a lot of the AppleScript documentation from Apple contains code that is poorly written and is part of the reason a lot of bad AppleScript persists so pervasively on the internet. Some of the documentation provided by Apple is also flat out wrong, which makes it very difficult for people to learn from and to know whose advice to take.
The code I have provided above, I assure you, is very much complete. Read on for a pretty boring explanation that I wish I didn't have to give, but now have to:
You'll see a lot of people do this:
set fh to open for access filepath with write permission
write somedata to fh
close access fh
It's not wrong, per se, but it's a pretty draconian way of using these read/write commands, which persists among many simply because they've always done it that way, and only seen it done that way. It's cumbersome and completely unnecessary1.
#user3439894 alluded to a try statement, which would, indeed, be needed for this method. open for access opens a file handle that was necessary in order to write out to a file. However, if an error in the script occurs during the write process, then the file handle is left dangling because the script terminates before being able to close access to the file. The way around this would be to use a try statement whose purpose was to catch an error, and ensure the close access command still gets executed:
try
set fh to open for access filepath with write permission
write somedata to fh
close access fh
on error
close access fh
end try
open for access and close access are redundant. There's no need to use them, and in fact, their use is only creating a potential problem that then needs a workaround in order to solve.
write--and read--are safe and advisable to use on their own, and they don't need a try block to catch potential errors. So why do the two commands I just called redundant appear in my script at all ?
close access (open for access fp)
One nifty side effect of the open for access command is that it will create a file at the specified path if one doesn't already exist. This is really useful because it negates the need to call out to System Events or (shudder) Finder to do this for you. It also has seems to have a wider scope for creating files at locations that other AppleScript applications don't have permission to access if they're sandboxed, etc.
Once open for access has created the file--or opened one that already exists--it returns a reference to the file handle. Then close access is used to immediately destroy file handle, because we don't actually have any use for it, but also don't want it left open. There's also no need for a try block: any error that could possibly arise would do so during the creation of the file handle, which would mean that the file handle won't be created, and so cannot be left open.
1The way the data gets written out now is fundamentally different to how it was written out when these commands were first introduced, and previously, write would not be able to access a file without first explicitly opening a file handle to it and declaring the need for writing permissions. Now, everything gets handled within the write command itself, including clean up.
I am using NSUserAutomatorTask to launch an Automator workflow from a macOS app.
I'm passing in variables via the variables property:
https://developer.apple.com/documentation/foundation/nsuserautomatortask/1418099-variables
Inside the Automator workflow I will then use Get Value of Variable actions to grab the variables that were set on the NSUserAutomatorTask and pass the variables to subsequent Automator actions:
Code looks like this: (simplified; I also check that the variables exist in the workflow)
let workflow = try! NSUserAutomatorTask(url: url)
workflow.variables = [
"singleFilePath": "/image1.png",
"multipleFilePaths": ["/image1.png", "/image2.png"],
]
workflow.execute(withInput: nil)
I'm able to print out the variable values in an alert via an Ask for Confirmation action:
The String variable's value is a simple string: /image1.png
The [String] array variable's value is wrapped in parentheses and each item is quoted:
(
"/image1.png",
"/image2.png"
)
I now run the pictured Automator workflow that first gets a variable's value and then attempts to open those Finder items.
The singleFilePath var works. It's passed on to the Open Finder Items action, and that file is opened by the default application.
Passing multipleFilePaths in the same manner does not work. No files are opened. Automator displays the error:
The action "Open Finder Items" was not supplied with the required data.
The action is "Open Finder Items", so there must be some way to pass multiple file paths to this action.
My questions and solutions in order of preference are:
Why is the default variable array/list format not working when passed to the subsequent action? Is there any way to pass or parse the array variable in a compatible format?
Can we use a single Run AppleScript action to reformat the array variable into a format that can be passed to subsequent Automator actions? (I'd like to continue to chain Automator actions rather than run pure AppleScript).
Open Finder Items actions can open an array of items if used in a normal non-variables workflow. And it's seemingly in the exact same format.
As a control to test this, rather than the variable, I'm using the Get Specified Finder Items action.
I then use a View Results action to inspect what is sent to Open Finder Items.
The results are seemingly exactly the same as when I parse the variable:
(
"/image1.png",
"/image2.png"
)
This workflow does correctly run the final action and open the files.
So Open Finder Items should work with the data I'm setting in the variable. I'm unsure why the variable data cannot be opened but manually-picked files can.
I opened a DTS ticket for this issue and received the following response:
I’m sorry to say the engineers working on this have confirmed that yes, it’s a bug, and no, there isn’t a workaround for sandboxed apps. They’ve also noted this isn’t a regression in behavior from previous releases.
Here is the thing:
I'm using AppleScript in Automator to get the clipboard value, and of course it works, but when I want to get multiple separated value, it always returns me only one value on the top。
Here is the step:
In Automator, import multiple "Get Value of Variable" actions, and
in these actions, I will set multiple values, all of these values
are e-mail format
Import an action named “Ask For Confirmation”, without this action,
I can’t pass multiple values to the next action “Choose from list”(I
don’t know why, but it works)
Import an action named “Choose from list” to let users choose the
e-mail values I’ve pre-set in this Automator application
Import another action named “Set Value of Variable” to get the
values users have chosen
Import an action named ”Copy to Clipboard” to copy these values to
clipboard
Import an action named “Run AppleScript” and here is my code:
on run {input, parameters}
--get the clipboard info
set Storage to get the clipboard
display dialog Storage
return input
end run
I've tried to copy some text_1, text_2 ... manually(command+c, command+v) and then run my AppleScript only, and it turns out the result what I really want like this:
Here is my Script Editor code:
I have to say, due to some limitation I can only use Automator and AppleScript,so is there any solution or suggestion?
Here is the "Get Value of Variable" picture
Get Value of Variable
Possible Explanation:
I believe this is a bug in either the Automator Copy To Clipbard action or AppleScript. Automator actions are often written in Objective-C, and it has some data types that AppleScript doesn't. It looks like the Automator action copies an array to the clipboard, which is something you can do with Objective-C, but not with AppleScript.
My feeling is that AppleScript is the entity at fault here, as the action is doing what it is meant to, and within the Automator context, it wouldn't pose a problem keeping the clipboard's data as an array type. AppleScript likely hasn't catered for this in its implementation of clipboard data handling, and does a poor job of coercing the array or list into plain text, which—as you stated—only contains the first element of the array.
Solutions:
1. Use a do shell script command
Instead of:
set Storage to get the clipboard
try:
set Storage to do shell script "pbpaste"
2. Use AppleScriptObjC
Since the Automator action is probably written in ObjC, it's reasonable to assume that using AppleScriptObjC will give us access to the necessary data types.
Replace your entire AppleScript with this:
use framework "Foundation"
use scripting additions
set Storage to (current application's NSPasteboard's generalPasteboard's ¬
stringForType:(current application's NSPasteboardTypeString)) ¬
as text
display alert Storage
3. Access the data through the input variable
The Run AppleScript action in Automator takes the result of the previous action and stores it in the variable attached to the on run {input, parameters} handler, namely input (you can ignore parameters).
Currently, your workflow actually sends the contents of the clipboard (the output of the Copy To Clipboard action) directly to the input variable of your AppleScript.
Therefore, you can replace the entire AppleScript with this:
on run {input, parameters}
set the text item delimiters to linefeed
set Storage to the input as text
display dialog Storage
end run
Any one of these solutions should work, so just choose your preferred method. Number #3 probably makes most sense in your current set up, and is the simplest.
I've seen a lot of questions about this on the interwebs but no answers. Is there a way to refer to an Automator 'variable' within AppleScript? I'd like to do some string manipulation as part of a workflow. I've worked around this by using Get Variable and passing them into temporary files, but it's kind of ugly.
I was trying the same ting as Steven. My conclusion is that when you run a flow inside the "Automator" application then your applescript can access Automator-varaibles via the Apple Script "Automator Suite" interface. For example:
set my_variable to value of variable "The Variable" of workflow 0 of current application
display dialog my_variable as text
set my_variable to "Test"
But if you save the flow as a stand alone application then it does NOT include the "Automator Suite" into the application and therefore the above script will no longer function :-(
An AppleScript used in a workflow accepts two parameters: input, or the output of the previous workflow, and parameters, the options set in the workflow's UI (if applicable). If the string you are manipulating is part of the workflow's input, it will be in input.
More information is available here.
I'm starting to poke around with Applescript and am looking at writing a few scripts for managing windows. A common task they will all need is to get the current screen size.
I've created a screen_size subroutine that seems to work, and I want to be able to share that with all my scripts. However, I can't figure out a way to put that in a separate file that I can load in my other scripts. I tried creating a separate screen_size.scpt file and use load script "screen_size.scpt", but I get an error about "can't make "screen_size.scpt" into a type file".
There has to be a way to do this, but I haven't been able to find anything online about how to do it.
EDIT:
The POSIX stuff suggested isn't working for me. I'm able to create the file object, but it refuses to convert to an alias, saying it can't find the file (looks like the POSIX file stays relative instead of expanding fully).
I found a suggestion online to use Finder, and have gotten the following working to get an alias:
tell application "Finder"
set _myPath to container of (path to me) as text
end tell
set _loadPath to (_myPath & "screen_size.scpt")
set _loadAlias to alias _loadPath
However, the next line fails with a syntax error, claiming that _loadAlias isn't a variable:
property _ScreenSize : load script _loadAlias
Every variation of this I've tried (doing the alias in the load call, etc) fails, always claiming the variable doesn't exist, even though I know it's being set and working as I can display it. What's going on? Why is it claiming a variable doesn't exist when it obviously does?
AppleScript is doing some really weird things when saving and I haven't figured out what's going on, but I ended up getting something to work.
Here's what I have:
on load_script(_scriptName)
tell application "Finder"
set _myPath to container of (path to me) as text
end tell
set _loadPath to (_myPath & _scriptName)
load script (alias _loadPath)
end load_script
set _ScreenSize to load_script("screen_size.scpt")
set _bounds to _ScreenSize's screen_size()
-- ...
The other answers were saying to set _ScreenSize as a property, but that would cause a syntax error which prevented me from ever saving the file. When I did it just using set it worked.
I wasn't ever able to get the POSIX path stuff suggested to work, but poking Finder for the path worked fine.
In order to execute an action from another script, you'll have to create an handler in the script you're going to load (in your answer you already did this with "screen_size()".
In your case this script will be "screen_size.scpt".
So "screen_size.scpt" will have to look something like this:
on screen_size()
--your actions
return [yourvalue] --the value you want to pass to the other script
end screen_size()
The script you'll load it from will have to look like this:
tell application "Finder"
set _myPath to (container of (path to me) as text & "screen_size.scpt") as alias
end tell
set _ScreenSizeScript to load script _myPath
set _bounds to _ScreenSizeScript's screen_size()
If it doesn't work, or you don't understand me completely, feel free to ask (:
Yes there is a way to do this. Load the file into a property and access it that way
property _ScreenSize : load script (alias "pathtoscript")
_ScreenSize's doStuff()
and for relative paths try this:
set p to "./screen_size.scpt"
set a to POSIX file p
so perhaps this will work:
set p to "./screen_size.scpt"
set a to POSIX file p
property _ScreenSize : load script (alias a)
_ScreenSize's doStuff()
I have people using my libraries on a daily basis, so I first ensure the library is here before calling it.
Let's say I have a library "Lib.Excel.app" (save as non-editable application with Satimage's Smile).
At the beginning of a script that makes use of it, I "load" the library by using this code :
set commonCodeFile to (path to library folder as string) & "Scripts:CommonLibraries:Lib.Excel.app"
tell application "Finder"
if not (exists (file commonCodeFile)) then error ("\"Lib.Excel\"
" & "
should be found in folder
" & "
scroll > CommonLibraries")
end tell
global cc -- make it short and easy to write :)
set cc to load script alias ccFile
Then when I have to use a function from the lib, I just call it like this :
set {what, a} to cc's veryNiceFunction()
Yes you can. You need the full path to the script however.
I believe you can still use "path to me" to get the path to the app executing the current script, and you can then modify that path to point to your sub-folder containing the scripts.
I used this technique to get around AppleScripts (former) 32k text size limits years ago for some really large/complex IRC scripting.
I think I still have all those old scripts in my G4, which is under the desk in my office at work. Sadly it's behind a Enet switch and I can't VNC into it otherwise I'd have tons of sample code to post.
You CAN load the script in a variable, but you have to declare it first.
property _ScreenSize : missing value
tell application "Finder" to set _myPath to container of (path to me) as text
set _loadPath to (_myPath & "screen_size.scpt")
set _loadAlias to alias _loadPath
set _ScreenSize to (load script _loadAlias)