I am trying to make script in automator to automate some shell scripts. I want the folder location to be dynamic. I have managed to pass the variable from bash to applescript, but applescript is giving a type error (-1700) "Can't make type into text". What am I missing here?
x="/"
my_command="cd $x"
osascript -e 'on run my_command' -e \
'tell application "Terminal"
do shell script my_command
activate
end tell' -e 'end run' $my_command
Here the command ('cd /') won't run and it gives me the following type error.
47:67: execution error: Terminal got an error: Can’t make {"cd", "/"} into type text. (-1700)
I am presuming your code block is being used inside an Automator Run Shell Script action (which is an important thing to include in your question, for future reference). Working on this assumption to address your specific query, there are two small tidbits to be aware of with regards to variables in bash and AppleScript:
In bash, the shell will process parameter expansions (such as your use of the $my_command
variable as a parameter for osascript
on the final line of your script) and command substitutions (where a variable is used in a similar way to that of a parameter, except it contains a shell command), and performs something called word splitting. As the name suggests, it splits text into words, although in the context of shell scripting, a word is delimited by any character defined in by the $IFS
environment variable. You can read about that more yourself if you're particularly interested, but what this does in your script is what you'd predict from something called word splitting: it splits "cd /"
at the whitespace, and provides two separate fields for each word, thus passing two arguments to osascript
, and not the one argument you were intending.
In AppleScript, parameters passed to the run
handler can be defined in two general syntax forms. The first is:
on run args
where args
is a variable that will be populated with a non-specific number of arguments (which may be zero) passed to it and generating a list
object, where each item
in the list is one of the arguments (order is preserved).
The second syntax form encloses variable identifier(s) within curly braces to declare a list of a fixed number of parameters:
on run {arg}
Here, {arg}
is a single-item list
object, defining a precise number of arguments (in this case, one), which will be passed into the variable arg
. Passing fewer than one arguments will throw an error in AppleScript; passing more will store the first argument in arg
, discarding the rest. To declare two arguments are expected:
on run {_1, _2}
where the first parameter is passed into the variable _1
, and the second is passed into the variable _2
(the choice of variable names in all of these examples hold no significance beyond being symbolic, but they are also examples of valid AppleScript variable identifiers). Passing fewer than two parameters will throw an error; passing more than two will destroy all but the first two parameters.
You can follow the same rules as you increase the number of parameters you declare in your handler, in the general form of:
on run {a, b, c, ...}
Note, additionally, these variables can, themselves, contain a list
of objects. Therefore, whilst on run {arg}
will receive a single argument, stored in the variable arg
, that argument may be a list containing several items.
Taking these two features of the two different languages into account, perhaps you can see what has happened: $my_command
undergoes parameter expansion, splitting it into two parameters, "cd"
and "/"
, that get passed to osascript
; osascript
interprets each individual parameter as an item
in a list
to be sent to the run
handler. Thankfully, you employed the first AppleScript run
handler syntax in declaring your parameter as a list
object of indefinite length.
This allowed the AppleScript variable my_command
to be passed a two-item list, i.e. {"cd", "/"}
. This is acceptable should you know how you want to handle the items in the list: in this case, you would want to concatenate them with a space delimiter; the straight forward way would be like so:
item 1 of my_command & space & item 2 of my_command --> "cd /"
As this wasn't done, do shell script
(which is the wrong command, anyway) received a list
object as its direct parameter, whereas it expects a text
(string
) object.
The way I would perhaps choose to fix this particular issue is to prevent the word splitting in bash. The way to do this is to enclose the parameter in double quotes:
osascript -e 'on run my_command' ... -e 'end run' "$my_command"
Double quotes tell the shell to treat everything inside as a single word, so now the AppleScript variable my_command
receives a single argument. This is still technically a list, containing one item, but most commands that expect text
objects are able to behave sensibly when it receives a single-item list that contains a string.
Then, as @user3439894 noted in a comment, you should replace do shell script
with do script
, and it will execute the command "cd /"
in a new Terminal tab/window.
The adjusted script implementing this fix might look something like this:
x="/"
my_command="cd $x"
osascript -e '
on run my_command
tell application "Terminal"
do script my_command
activate
end tell
end run' "$my_command"
All that being said, in this situation, it would be more sensible to use a Run AppleScript action instead, which would look like this:
on run {my_command, null}
tell application "Terminal"
do script my_command
activate
end tell
end run
Here, I'm declaring the run
handler to be passed exactly two parameters:
The second one is something specific to Automator, which usually has a sample bit of code ready to store the second argument in a variable called parameters
. This argument is passed by Automator, and so you don't need to worry about where it's coming from. It contains local directory information related to the running of this script instance. It's not especially useful in any situations I've ever come across, and certainly not in this one. I prefer to destroy the information by declaring the second parameter as null
.
This leaves us with only one parameter, but this parameter will arrive in the form of a list
, which I would recommend—at the beginning before you decide you want more complexity—to limit to a single item, which would be a string containing your bash expression.
This can be fed to the Run AppleScript action by any action that comes before it, which I recommend be either plain text; an Automator variable; or something specific to the nature of the parameter (in this case, a directory, suggesting a Finder action would be appropriate). As you wish to keep the directory dynamic, the Get Selected Finder Items would be one example of how to feed Automator a Finder path, and have that become the parameter passed to the AppleScript.
Therefore, I'm going to make a few small changes, starting with introducing the cd
command into the AppleScript to save you having to muddle with additional Automator actions and work out how to combine them properly. I'm also going to modify the run
handler parameter expression:
on run {{filepath}, null}
local filepath
set filepath to the POSIX path of ([filepath, "::"] as text)
tell application "Terminal"
do script "cd " & the quoted form of the filepath
activate
end tell
end run
This workflow now receives the file path for what file, folder, or items you have selected in the Finder, and opens a Terminal window to point its working directory at the folder in which the selected file(s) are contained.
It's by no means illustrating a perfect implementation of this (and, in face, macOS has a built in service to open a Terminal window pointed at the current open folder in Finder). But, for a 5-line AppleScript code block that communicates with a shell, it's workable.
This nifty and wild expression for my parameter declaration:
on run {{filepath}, null}
added curly braces around the filepath
parameter, thereby limiting it to precisely one, unary file path argument. It eliminates having to worry what happens about multiple selections being made, as only the first item in the selection gets stored. It means that selecting no items and triggering the script will throw an error, but the reverse is true if you remove the braces (multiple selections that throw an error).
There are many ways to manage all scenarios happily, but it would take this answer beyond the scope of what was originally intended.