osascript using bash variable with a space
Asked Answered
K

2

22

I am using osascript in Bash to display a message in Notification Center (Mac OS X) via Apple Script. I am trying to pass a text variable from Bash to the script. For a variable without spaces, this works just fine, but not for one with spaces:

Defining

var1="Hello"
var2="Hello World"

and using

osascript -e 'display notification "'$var1'"'

works, but using

osascript -e 'display notification "'$var2'"'

yields

syntax error: Expected string but found end of script.

What do I need to change (I am new to this)? Thanks!

Kenway answered 28/5, 2014 at 22:39 Comment(0)
B
38

You could try to use instead :

osascript -e "display notification \"$var2\""

Or :

osascript -e 'display notification "'"$var2"'"'

This fixes the problem of manipulation of variables that contains spaces in bash. However, this solution doesn't protect against injections of osascript code. So it would be better to choose one of Charles Duffy's solutions or to use bash parameter expansion :

# if you prefer escape the doubles quotes
osascript -e "display notification \"${var2//\"/\\\"}\""
# or
osascript -e 'display notification "'"${var2//\"/\\\"}"'"'

# if you prefer to remove the doubles quotes
osascript -e "display notification \"${var2//\"/}\""
# or
osascript -e 'display notification "'"${var2//\"/}"'"'

Thank to mklement0 for this very useful suggestion !

Boggers answered 28/5, 2014 at 22:47 Comment(17)
This isn't safe to use with untrusted data: Consider what happens when var2 contains literal " characters.Farris
@CharlesDuffy I don't know osascript. However in bash scripting, we must always protect our variables by using double quotes in most cases (because of the default IFS). Data consistency and security for osascript is not addressed in my answer and it's not the subject although I assume that there are risks of injections as for other technologies. In bash scripting, it's not a problem to have a string which contain double quotes.Boggers
I didn't say it was a bash problem, I said it was an injection problem. Think Bobby Tables -- this is the local equivalent of "SELECT * FROM USERS WHERE name='$foo'", and dangerous for all the same reasons.Farris
...to clarify the Bobby Tables reference: xkcd.com/327; to clarify the security bug: think about what happens with your solution when var2='message to print"; command("rm -rf /"); display notification "' (or whatever the exact osascript syntax is for that).Farris
...and as for "X is not addressed by this answer" -- just as any answer for generating SQL that allowed injection bugs should be subject to jeers from the crowd, answers for a question on how to properly interface between bash and osascript that don't actually take proper/safe use of osascript into account have good cause to be similarly rejected.Farris
@CharlesDuffy You're right, I would have reacted the same way if it were an unsecured SQL query. I'll add a note in my answer.Boggers
@CharlesDuffy I've updated my answer and gave +1 to your answer ;)Boggers
@CharlesDuffy: I wouldn't know how to inject code in this instance (AppleScript doesn't allow placing multiple commands on 1 line; in general, though, the concern about injection is appreciated); in particular, however, the concern about values containing " is valid (in terms of breaking the command); here's the (somewhat cumbersome) remedy, using bash parameter expansion: osascript -e "display notification \"${var2//\"/\\\"}\""Farfetched
@mklement0, AppleScript does allow string concatenation, though -- I'd think that var2='" & arbitrary_command & "' would do the trick, even if you don't have a ;-equivalent operator.Farris
@CharlesDuffy: I wasn't referring to string concatenation, I was referring to placing multiple statements on a single line (single -e option) - which, it seems to me, would be the only way to execute arbitrary code in this scenario. AppleScript does have a feature akin to eval - run script - but that would obviously also require a separate command. I'm genuinely curious: can you come up with an injection example?Farfetched
@mklement0, var2='hello world" & do shell script "touch /tmp/created" & "'; osascript -e 'display notification "'"$var2"'"' ; when I run that, I get /tmp/created on disk. Which is to say that post-escape string concatenation can, in practice, be used to run arbitrary code.Farris
@IdrissNeumann, would you consider adopting mklement0's suggestion re: using parameter expansion to escape any double quotes? I'd feel a lot more comfortable with your answer at the top of the page were that done.Farris
@CharlesDuffy Well done, hadn't thought of that. With escaped double quotes, however, as I suggested (${var2//\"/\\\"}), this exploit will NOT work - or do you see a way around that, too?Farfetched
@mklement0, ...and that's why I asked Idriss to incorporate your suggestion, rather than to withdraw the answer. I don't have an immediate escape for that, though if I were going to try to create one, I'd probably start poking around Unicode character set conversion logic (being a common weak point), and/or reading the formal language spec.Farris
...well, what I'd really do in practice would probably be to ask someone on the reverse engineering team at work to take a shot at it. :PFarris
...as an aside: Part of the reason I'm maintaining my own answer, rather than entirely endorsing this one as amended, is that despite using the Bobby Tables XKCD link to demonstrate injection attacks to others, I actually object to it somewhat: "Sanitiz[ing] inputs", but still mixing syntax and data, is a far weaker safety measure than segregating user-provided data from syntax entirely.Farris
Oddly enough, I'm using the $SRCROOT variable calling a shell script to open a Terminal window from an Xcode project. Nothing listed above allows me to display dialog with the $SRCROOT value in the message body. The dialog doesn't display at all when attempting to add $SRCROOT. The code below works as expected, but not when adding $SRCROOT to replace message. osascript -e 'display dialog "message" with title "Hi"' open -a Terminal "$SRCROOT"Atlantic
F
20

This version is completely safe against injection attacks, unlike variants trying to use string concatenation.

osascript \
  -e "on run(argv)" \
  -e "return display notification item 1 of argv" \
  -e "end" \
  -- "$var2"

...or, if one preferred to pass code in on stdin rather than argv:

osascript -- - "$var2" <<'EOF'
  on run(argv)
    return display notification item 1 of argv
  end
EOF
Farris answered 28/5, 2014 at 22:53 Comment(7)
+1 for a robust solution for passing arbitrary arguments to osascript safely from the command line (though it may be overkill for the case at hand).Farfetched
Thanks for the more robust solution - I ended up accepting the other one since it is sufficient for the case at hand, where no injection attacks or variables with ` are to be expected.Kenway
There's no real reason not to use proper, safe code; when you don't expect an attack is when you are most vulnerable to it.Hot
As an aside: the on run argv line works, despite not enclosing the argument - argv - in parentheses, which is normally required in AppleScript handlers. This curious syntax exception - which only works with handler name run - has been around for a long time (and therefore probably won't go away). The non-exceptional syntax works too, however: on run(argv).Farfetched
@chepner: Excellent point. Fortunately, those variants of the accepted answer with escaping or removal of double quotes should be safe.Farfetched
apologies if I should rather have "accepted" the other answer - I just clicked on the one which I ended up using. Should I change the accepted answer so that others with a similar problem come to the "safer" solution right away?Kenway
@Bernd, now that the other answer has been amended to show a safer approach, it's probably OK to leave as-is... and I say "probably" only because it retains the unsafe first approach rather than showing exclusively the latter two answers now given (which are, if not certainly safe, at least probably safe).Farris

© 2022 - 2024 — McMap. All rights reserved.