Search code examples
bashapplescriptautomator

Passing variables from bash to applescript - can't make into type text. (-1700) Error


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)

Solution

  • 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:

    1. 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.

    2. 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
    

    Automator Workflow in macOS 10.13

    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.