deanishe Posted June 9, 2016 Posted June 9, 2016 (edited) Note: This post is out of date as of Alfred 3.6 (which introduced AppleScript functions to set and remove variables). There is an updated version on my own site: Workflow/environment variables in Alfred. This post will not be updated due to the difficulty of editing complex posts using the forum software. Sorry. This is a brief look at how to get, set and save variables in code (i.e. in Script Filters, Run Script Actions, etc.). In Alfred 2, you had one single variable to work with: the {query} macro. Alfred 3 adds the ability to specify as many variables as you want. Alfred's own help provides a great description of working with variables in Alfred's own UI. I'm going to look more closely about getting and setting workflow/environment variables in your own code within a workflow. First of all, it bears mentioning that all variables are strings. Sure, you can set a variable to a number in JSON, but when it reaches your next script or one of Alfred's Filter Utilities, it will be a string. If you set a variable to an array (e.g. [1, 2, 3, "mach dat Mäh mal ei"]), Alfred will turn it into a single, tab-delimited string ("1\t2\t3\tmach dat Mäh mal ei"). Setting variables There are several ways to set variables. The most obvious ones are in the Workflow Environment Variables table in the workflow configuration sheet and using the Arg and Vars Utility. The configuration sheet is largely without magic, but in an Args and Vars Utility, you can use variable expansion macros: {query} expands (as always) to the input (which may be a user-entered query or the output from a previous Action), and you can use {var:VARIABLE_NAME} macros for your own custom variables. This is described in detail in the above-mentioned help pages. More interestingly, you can also set variables via the output of your scripts (i.e. dynamically) by emitting appropriate JSON. How you set variables depends on whether you are using a Script Filter or a Run Script action. You must use the appropriate mechanism, or it won't work. From Run Script actions Let's say your script outputs a URL, e.g. https://www.google.com. Normally you just do print('https://www.google.com') (or echo or puts) and that gets passed as the input to the next action. To also pass variables, you instead emit JSON in a very specific format: {"alfredworkflow": { "arg": "https://www.google.com", "variables": {"browser": "Google Chrome"}}} The root alfredworkflow object is required. If it's missing, Alfred won't parse the JSON, but will pass it as-is as input to the next action (which can also be very useful). Your output (i.e. the next Action's input/{query}) goes in arg, and any variables you wish to set go in the variables object. From Script Filters You can also set workflow variables via Script Filter feedback at three different levels: the root level, the item level and the modifier level. (Note: This only applies to JSON feedback; XML feedback is now deprecated and does not support the features described here.) In each case, variables are set via a variables object at the appropriate level (feedback root, item or mod). Root-level variables Root-level variables are always passed to downstream elements regardless of which item is actioned. They are also passed back to the same Script Filter if you've set rerun, so you can use root-level variables to implement a progress bar. browser is set to Safari for all items: {"variables": {"browser": "Safari"}, "items": [{"title": "Google", "arg": "https://www.google.com"}]} Item-level variables Item-level variables are only passed downstream when the item they're set on is actioned, and they override root-level variables. Root-level variables are also passed downstream when you action an item. browser is set to Safari by default, but Google Chrome for Reddit: {"variables": {"browser": "Safari"}, "items": [ {"title": "Google", "arg": "https://www.google.com"}, {"title": "Reddit", "arg": "https://reddit.com", "variables": {"browser": "Google Chrome"}}]} Modifier-level variables Modifier-level variables are only passed downstream when the corresponding item is actioned with the appropriate modifier key pressed. They replace item- and root-level variables (i.e. if a modifier sets any variables, Alfred ignores any root- and item-level variables). As above, browser is set to Safari by default and Google Chrome for Reddit. But you can also pass browser=Google Chrome for Google by holding ⌘ when actioning it: {"variables": {"browser": "Safari"}, "items": [ {"title": "Google", "arg": "https://www.google.com", "mods" {"cmd": {"variables": {"browser": "Google Chrome"}}}}, {"title": "Reddit", "arg": "https://reddit.com", "variables": {"browser": "Google Chrome"}}]} Using variables So you've set a few variables, and now you want to use them. Within Alfred elements like Arg and Vars or Filter Utilities, you use the above-mentioned {var:VARIABLE_NAME} macros. Very simple. Where it gets a little more complicated is in your own code. First and foremost, {var:VARIABLE_NAME} macro expansion does not work in Run Script Actions (or Run NSAppleScript). When Alfred runs your code, it does not use {var:...} macros, but rather takes any workflow variables and sets them as environment variables for your script. Using the above example again, Alfred would pass "https://www.google.com" to my script as input (either via ARGV or {query} depending on the settings) and it would set the environment variable browser to Safari or Google Chrome. How you retrieve environment variables depends on the language you're using. Accessing environment variables in different languages In bash/zsh, the variables are already in the global namespace. Just use $browser In Python, use the os.environ dictionary or os.getenv('VARIABLE_NAME'): import os browser = os.environ['browser'] # Or browser = os.getenv('browser') In AppleScript, use system attribute: set theBrowser to (system attribute "browser") In JavaScript (JXA), use $.getenv(): ObjC.import('stdlib') var browser = $.getenv('browser') In PHP, use getenv(): (Please see this comment by juliosecco on why you should use getenv() over $_ENV.) $browser = getenv('browser'); // Or $browser = $_ENV['browser']; In Ruby, use ENV: browser = ENV["browser"] Saving variables NOTE: This section is out of date as of Alfred 3.6. Please see the updated version linked at the top of the post. As amoose136 points out, any variables you set in a running workflow are not saved. They exist as long as the workflow is running and then disappear. Any Workflow Environment Variables will "reset" to their values in the workflow configuration sheet on the next run. Generally, this is what you want, but sometimes you want to save a variable's value. For example, you might have an API_KEY Workflow Environment Variable in the configuration sheet. The user can enter their API key for the service in the configuration sheet, but you'd also like to add the ability to set it from within your workflow, e.g. with a setapikey Keyword and corresponding Run Script action. WARNING: As of Alfred 3.4.1, Alfred takes several seconds to notice when info.plist has been updated by something other than itself. As a result, relying on altering info.plist programatically can be problematic, as Alfred won't notice the changes for several seconds (5–10 seconds is typical on my machine). If you update a workflow variable in info.plist and run your workflow again immediately, it is unlikely that Alfred will have picked up the change. The Workflow Environment Variables are contained in the variables section of info.plist. Consequently, to update them, you need to update info.plist. Regardless of which language you're using, you can call the PlistBuddy program to alter Workflow Environment Variables: # Set a variable /usr/libexec/PlistBuddy -c "Set :variables:API_KEY \"ABC-XYZ\"" info.plist # Delete a variable (i.e. remove it entirely from Workflow Environment Variables) /usr/libexec/PlistBuddy -c "Delete :variables:API_KEY" info.plist In Python, there's no need to use an external program, as it has the built-in plistlib library: from plistlib import readPlist, writePlist # Read info.plist into a standard Python dictionary info = readPlist('info.plist') # Set a variable info['variables']['API_KEY'] = 'ABC-XYZ' # Delete a variable del info['variables']['API_KEY'] # Save changes writePlist(info, 'info.plist') (Note: plistlib only works with XML property lists, which is fine for info.plist, but using PlistBuddy is generally more robust.) Don't forget: any changes you make to info.plist only take effect the next time the workflow is run. This likely doesn't matter in most cases, but if you need a variable to be updated immediately (i.e. also for the current workflow run), you must also set it "live" using one of the methods described in the Setting variables section above. Edited August 17, 2019 by deanishe Link to updated version FroZen_X, artdev, Carlos-Sz and 5 others 6 2
FroZen_X Posted June 9, 2016 Posted June 9, 2016 Good job deanishe! This is a good help and would be cool to see as an info in Alfred like the script editor itself(maybe in gray or so). Maybe its even possible that Alfred handles this itself, even tho i know it just runs the script. Like it would check for vars and use the associated call to get the var. However, now its documented and can be done this way! Good job GMN 1
deanishe Posted June 9, 2016 Author Posted June 9, 2016 Like it would check for vars and use the associated call to get the var. This is something I'm considering adding to Alfred-Workflow, kinda like an HTTP session. Version 2 is going to be a straight port of the v1 API to Alfred 3 (although it's very different internally), but I hope to add something more useful to v3 once we've all figured out the best way to work with Alfred 3. When Andrew adds internal triggers, it might be possible to build quite a powerful API on top of them. FroZen_X 1
juliosecco Posted June 10, 2016 Posted June 10, 2016 Really useful, deanishe! thanks for taking the time to post this, now I've this stuff a lot clearer Giulio
amoose136 Posted June 10, 2016 Posted June 10, 2016 Very useful but outputting json like that doesn't appear to change the env variables that are stored. For most workflow variables this is fine but for some that don't need to be updated every call you need persistent storage. To do that I have been (in python) doing: import plistlib as pl bar = new_variable_content temp = pl.readPlist('info.plist') temp['variables']['foo'] = bar pl.writePlist(temp,'info.plist')
deanishe Posted June 10, 2016 Author Posted June 10, 2016 Very useful but outputting json like that doesn't appear to change the env variables that are stored. Good point. I'll add a section about persisting variables.
Andrew Posted June 18, 2016 Posted June 18, 2016 When Andrew adds internal triggers, it might be possible to build quite a powerful API on top of them. There is a very good chance that when I add the internal trigger object (to call same, or other workflow, replacing the AppleScript loop back), that the current workflow stream variables will be passed through to the receiving object. I think that this will add a significant amount of power and allow for callback style calls between workflows. verbbis 1
Martien Oranje Posted June 30, 2016 Posted June 30, 2016 Thanks for the write-up, it clarifies a lot. I'm still struggling with retrieving plist variables though. I added the following entry to the info.plist like this: /usr/libexec/PlistBuddy -c "add :variables:awt_language: string \"en\"" info.plist Should this awt_language and it's value "en" be retrieved like: ObjC.import('stdlib'); var awt_language = $.getenv( 'awt_language' ); // Or perhaps var awt_language = $.getenv( 'variables.awt_language' ); // Or even var variables = $.getenv( 'variables' ); // variables.awt_language None of these worked for me so perhaps my assumption that it can be retrieved that way is wrong. Any help would be welcome, thanks Martien
deanishe Posted June 30, 2016 Author Posted June 30, 2016 /usr/libexec/PlistBuddy -c "add :variables:awt_language: string \"en\"" info.plist /usr/libexec/PlistBuddy -c "Add :variables:awt_language string \"en\"" info.plist
juliosecco Posted July 23, 2016 Posted July 23, 2016 (edited) Hi, I would like to share a thing I just discovered about environment variables and php: today I have changed my php.ini.default in php.ini to test a workflow with a 'developer' php warning level, and I was not more able to read environment variables using $_ENV['name']. I have discovered that in the php.ini there was a directive variables_order, its comment is: " This directive determines which super global arrays are registered when PHP starts up ... There is a performance penalty paid for the registration of these arrays and because ENV is not as commonly used as the others, ENV is not recommended on productions servers. Yo can still get access to the environment variables through getenv() should you need to." it was set to "GPCS" where the letters mean GET, POST, COOKIE, ENV and SERVER, and in its default value the E for ENV was missing, for that the $_ENV was not working anymore. Setting it to "GPCSE" made everything work again, but what I have learned is that using $_ENV and getenv() is not equivalent, being the latter better to be sure to correctly read the variable with every php.ini setting, so from now I will always use getenv(). hope this helps, Giulio Edited July 23, 2016 by juliosecco deanishe 1
jmb275 Posted September 7, 2016 Posted September 7, 2016 Hi deanishe- I've spent some time on this problem. Not sure what piece I'm missing. I have a bash script and I would like to emit a couple of variables. I thought I was doing what you said here but I can't get it to work. Here is my script. if [[ ${filename} == "" ]]; then filename="note.txt" fullpath=${filepath}/${filename} else fullpath=${filepath}/${filename} fi echo ${fullpath} >&2 #touch ${fullpath} #open ${fullpath} printf '{"alfredworkflow": {"arg": "%s", "variables": {"fullpath": "%s"}, {"filename": "%s"}}}' "$fullpath" "$fullpath" "$filename" Then I have this connected to a Large Type output and am trying to print {var:fullpath} but it's just blank every time. When I print {query} it shows the whole JSON fine, but as you said, it appears Alfred is not parsing the JSON object. I don't know why. Any help would be really appreciated. Thanks.
jmb275 Posted September 7, 2016 Posted September 7, 2016 Sorry all. I got it figured. Apparently knowing nothing about JSON makes for trouble. I realized that it should be printf '{"alfredworkflow": {"arg": "%s", "variables": {"fullpath": "%s", "filename": "%s"}}}' "$fullpath" "$fullpath" "$filename"
mbigras Posted June 30, 2017 Posted June 30, 2017 Very grateful for your tutorials deanishe! This helped me out ?
Chochek Posted September 5, 2017 Posted September 5, 2017 (edited) I can't get this to work. I have a "Run Script" action ("usr/bin/osascript (JS)") that parses the input and returns a JSON like you mentioned @deanishe return { "alfredworkflow": { "arg": `${libPath} ${projectPath}`, "variables": { "library": libPath, "project": projectPath } } } But the next "Run Script" action gets this string as the "argv" alfredworkflow:arg:LIB_AND_PROJECT_PATH_VALUES, variables:library:LIB_PATH_VALUE, project:PROJECT_PATH_VALUE Am I missing something? Edited September 5, 2017 by Chochek
deanishe Posted September 5, 2017 Author Posted September 5, 2017 You should output JSON, not an object. You probably need a JSON.stringify() in there, and you may need to write it to STOUT, not return it.
Chochek Posted September 5, 2017 Posted September 5, 2017 :facepalm: Thanks, JSON.stringify() did the trick!
nikivi Posted September 16, 2017 Posted September 16, 2017 (edited) Is it possible to somehow retrieve variables users set in configuration sheet (or via ARG setting in some Alfred object) in Go? I am using AwGo library and I want to save user's input somewhere but then access it again from code. Edited September 16, 2017 by nikivi
deanishe Posted September 16, 2017 Author Posted September 16, 2017 4 hours ago, nikivi said: Is it possible to somehow retrieve variables users set in configuration sheet Yes. Just google "golang environment variables".
deanishe Posted October 14, 2018 Author Posted October 14, 2018 I've posted an updated version of this post on my own site: Workflow/environment variables in Alfred. I tried to update this version, but the awful forum editor makes it too difficult to be worth the effort. DickDi and nikivi 2
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now