I have the following line for my Sublime text build system json:
"shell_cmd": "osascript -e 'tell application \"Terminal\" to do script \"ssh root@1.2.3.4 \"'"
It works fine, but now I should add -t 'ls -la; bash -l'
after ssh command. I wanted to escape '
characters with backslash (\'
), but Sublime doesn't accept it:
"shell_cmd": "osascript -e 'tell application \"Terminal\" to do script \"ssh root@1.2.3.4 \'ls -la; bash -l\'\"'"
I tried to replace "
with '
and vice versa, but it also doesn't work with Sublime.
What else can I try?
Upd. here is the command, which works well from the command line:
osascript -e 'tell application "Terminal" to do script " ssh root@1.2.3.4 -t \"ls -la; bash -l\" " '
Don't try to figure out quoting by hand when it can be at all avoided: instead, let tools do the work for you. For example, let's say you have a working shell command:
ssh 1.2.3.4 'cd .. && bash -l'
If you want to create a shell string containing that command, first put it in an array, then ask the shell to quote it:
cmdArr=( ssh 1.2.3.4 'cd .. && bash -l' )
cmdStr="${cmdArr[*]@Q}"
In bash 5.x or later, this will store something like 'ssh' '1.2.3.4' 'cd .. && bash -l'
in your string.
The next step would typically be to figure out how to escape the contents of cmdStr
to be safely substituted into osascript source code without risk of injection vulnerabilities -- but we can sidestep this entirely by passing cmdStr
out-of-band from the osascript source code, on the command line argument vector (argv). osascript source that looks on the argv for the text of a shell command to run might look like the following:
osascript -- - "$cmdStr" <<'EOF'
on run(argv)
return tell application "Terminal" to do script item 1 of argv
end
EOF
...but we want a string containing a shell command that runs that osascript code (with the shell string created by our previous escaping operation passed to it), right? Well, one way to assemble one might be:
# generate a shell string containing the command we want to run
cmdArr=( ssh 1.2.3.4 'cd .. && bash -l' )
cmdStr="${cmdArr[*]@Q}"
# text of osascript code that runs its first argument in terminal
osascript=$(cat <<'EOF'
on run(argv)
tell application "Terminal" to do script item 1 of argv
end
EOF
)
# one array per simple command; pass script on stdin to osascript
# ...and command on argv of osascript interpreter
write_script_arr=( printf '%s\n' "$osascript" )
run_script_arr=( osascript -- - "$cmdStr" )
# finally, assemble those pieces together
full_cmd_str="${write_script_arr[*]@Q} | ${run_script_arr[*]@Q}"
Okay! Now we just need to make that into JSON. Fortunately, that's something jq will do for you automatically:
jq -R '{"shell_cmd": .}' <<<"$full_cmd_str"
...and there we are! Output is something like:
{
"shell_cmd": "'printf' '%s\\n' $' on run(argv)\\n tell application \"Terminal\" to do script item 1 of argv\\n end' | 'osascript' '--' '-' ''\\''ssh'\\'' '\\''1.2.3.4'\\'' '\\''cd .. && bash -l'\\'''"
}
...and as long as your shell_cmd is executed by bash (not /bin/sh
, which doesn't support the $'...'
syntax!), it'll work perfectly.