Converting to one line AppleScript - applescript

I have a series of AppleScript commands I need to implement into a single line of AppleScript. The code is below.
delay 2
tell application "System Events" to keystroke "foo"
tell application "System Events" to keystroke return

You can combine the two keystroke commands into a single one by concatenating your strings together:
tell app "System Events" to keystroke "foo" & return
But that still leaves you with one command and a statement (delay and tell … to …).
Effectively, AppleScript’s statement separator is a line break. In modern AppleScript the line breaks can be either CR, LF, or CRLF (respectively: old-Mac-, Unix-, or DOS-style). There is no convenient way write a multi-statement line (like foo; bar; baz; in C, Perl, Ruby, et cetera).
For your specific request, you can combine the two in a really ugly way by using delay as a part of an expression.
tell application "System Events" to keystroke "" & (delay 2) & "foo" & return
This is ugly because delay technically returns no value, but we are able to concatenate it with an empty string without causing an error.
The problem is that such a construction is not very “general purpose”. You may not always be able to turn any command into an expression in the same way (and you can not use statements like tell as a part of an expression1). For a bit more brevity (and obfuscation), you can write the last bit as "foo" & return & (delay 2). It still runs in the same order (delay first) because each part of the concatenation expression must be evaluated before it can be built into a single value for the keystroke command.
1 Short of putting them in a string given to run script that is. Even then, some statements (loops, try/catch, etc.) are always multi-line; though you can get around that by using escaped line breaks or concatenation and one of the line break constants (as follows).
You could use run script with a string argument and use the backslash2 escapes to represent the line breaks.
run script "delay 0.1\ntell app \"System Events\" to keystroke \"foo\" & return"
AppleScript Editor may translate the escape notation into a literal unless you have “Escape tabs and line break in string” enabled in its Editing preferences (available in 10.5 and later). You could always use the return constant and concatenation instead of the in-string-literal/escape-notation.
run script "delay 0.1" & return & "tell app \"System Events\" to keystroke \"foo\" & return"
2 If you are going to represent such strings inside an Objective C string literal (as one of your comments might imply), then you will have to escape the backslashes and double quotes for Objective C, also (…& \"tell app \\\"System…).
Or, if you are ultimately trying to run the code with osascript, then you can use the fact that each instance of -e will become a separate line to put it all on a single shell command line.
osascript -e 'delay 2' -e 'tell app "System Events" to keystroke "foo" & return'

osascript with several -e 'line1' -e 'line2' -e 'line3' ...
The manpage for osascript suggests to use several -e to build up a multi-line script.
-e statement
Enter one line of a script. If -e is given, osascript will not look for a filename in the argument list.
Multiple -e options may be given to build up a multi-line script.
Because most scripts use characters
that are special to many shell programs (for example, AppleScript uses single and double quote marks, “(”,
“)”, and “*”), the statement will have to be correctly quoted and escaped to get it past the shell intact.
Answer already given by Chris Johnsen, but is contained in a longer answer and therefore can be overlooked.

Related

Applescript Occasionally Freezes Automator

I'm trying to use this applescript to modify a file path written to a text file, copy the modified path to the clipboard, set it as variable thePath, use it to pull a Google Drive link from a list, and then copy that link to the clipboard.
Running it within Automator, it sometimes works perfectly but other times it stalls while running this applescript and crashes textedit, and then eventually times out without any specific error messages. Are there any issues with my code that would be causing Automator to freeze?
Note that I've substituted the actual links with link1, link2, etc. for privacy reasons.
on run {input, parameters}
tell application "TextEdit" to activate
tell application "System Events"
key code 125
key code 123 using shift down
key code 123 using shift down
key code 123 using shift down
key code 123 using shift down
key code 123 using shift down
key code 123 using shift down
key code 123 using shift down
key code 123 using shift down
keystroke "x" using command down
keystroke "a" using command down
key code 51
keystroke "v" using command down
keystroke "s" using command down
keystroke "w" using command down
delay 2
end tell
set thePath to the clipboard
set myList to {"link1","link2","link3","link4","link5","link6","link7","link8","link9","link10","link11","link12","link13","link14","link15","link16","link17","link18","link19","link20","link21","link22","link23","link24","link25","link26","link27","link28","link29","link30","link31","link32","link33","link34","link35","link36","link37","link38","link39","link40","link41","link42","link43","link44","link45","link46","link47","link48","link49","link50","link51","link52","link53","link54","link55","link56","link57","link58","link59","link60","link61","link62","link63","link64","link65","link66","link67","link68","link69","link70","link71","link72","link73","link74","link75","link76","link77","link78","link79","link80","link81","link82","link83","link84","link85","link86","link87","link88","link89","link90","link91","link92","link93","link94","link95","link96","link97","link98","link99","link100","link101","link102","link103","link104","link105","link106","link107","link108","link109","link110","link111","link112","link113","link114","link115","link116","link117","link118","link119","link120","link121","link122","link123","link124","link125","link126","link127","link128","link129","link130","link131","link132","link133","link134","link135","link136","link137","link138","link139","link140","link141","link142","link143","link144","link145","link146","link147","link148","link149","link150","link151","link152","link153","link154","link155","link156","link157","link158","link159","link160","link161","link162","link163","link164","link165","link166","link167","link168","link169","link170","link171","link172","link173","link174","link175","link176","link177","link178","link179","link180","link181","link182","link183","link184","link185","link186","link187","link188","link189","link190","link191","link192","link193","link194","link195","link196","link197","link198","link199","link200","link201","link202","link203","link204","link205","link206","link207","link208","link209","link210","link211","link212","link213","link214","link215","link216","link217","link218","link219","link220","link221","link222","link223","link224","link225","link226","link227","link228","link229","link230","link231","link232","link233","link234","link235","link236","link237","link238","link239","link240","link241","link242","link243","link244","link245","link246","link247","link248","link249","link250","link251","link252","link253","link254","link255","link256","link257","link258","link259","link260","link261","link262","link263","link264","link265","link266","link267","link268","link269","link270","link271","link272","link273","link274","link275","link276","link277","link278","link279","link280","link281","link282","link283","link284","link285","link286","link287","link288","link289","link290","link291","link292","link293","link294","link295","link296","link297","link298"}
set the clipboard to item thePath of myList
return input
end run
You could do this with AppleScript, but it would be much more sensible to do this with a shell script, by way of the Run Shell Script action in Automator, selecting the option to pass the input "as arguments". The default shell in macOS is zsh, so stick with that.
The script is short and sweet:
( PATH=/usr/bin:$PATH
basename "$1" | bc
) 2>/dev/null
basename extracts last path component, which, in this case, is the name of the folder, even if the path ends with a trailing slash. The result of this command is then piped through to bc, which performs basic calculations: this is purely to parse the folder name as a number, so that "00007" simply returns "7".
How to make use of this value is entirely dependent on how and where you store the various links from which it selects. Here's one suggestion, which is reasonably simple:
This allows you to hard-code your 300 or so links into a single Automator variable, which a second shell script reads and returns the specific link associated (by line number) with the value obtained from the previous shell script. The link then placed onto the clipboard.

how to interpret bash variables in applescript command

I am trying to write a bash script that reloads a given chrome tab, and I am passing the variable POSITION_STRING to applescript to dynamically determine the definition of the statement (as I thought thats what heredocs notations are used to do).
But it seems applescript rejects this type of connotation, help?
declare -A POSSIBLE_POSITIONS
POSSIBLE_POSITIONS=(
["1"]="first"
["2"]="second"
["3"]="third"
["4"]="fourth"
["5"]="fifth"
["6"]="sixth"
["7"]="seventh"
["8"]="eighth"
["9"]="ninth"
["10"]="tenth"
)
# echo "${POSSIBLE_POSITIONS[$1]}"
POSITION=$1
POSITION_STRING=${POSSIBLE_POSITIONS[$POSITION]}
# echo $POSITION_STRING
/usr/bin/osascript <<EOF
log "$POSITION_STRING" # this works!
tell application "Google Chrome"
tell the "$POSITION_STRING" tab of its first window
# reload
end tell
end tell
EOF
AppleScript's object specifiers accept integer-based indexes just fine. There's absolutely no need to use first, second, etc keywords, and you're digging yourself a hole trying to munge them into AppleScript code.
When using osascript, the correct way to pass arguments into your AppleScript is to put them after the file name (if any). osascript will then pass these arguments to your AppleScript's run handler as a list of text values, which you can then extract, check, coerce, etc. as appropriate.
Example:
POSITION=1
/usr/bin/osascript - "$POSITION" <<'EOF'
on run argv -- argv is a list of text
-- process the arguments
set n to item 1 of argv as integer
-- do your stuff
tell application "Google Chrome"
tell tab n of window 1
reload
end tell
end tell
end run
EOF
Quoting rules are different in here docs than in the body of a bash script. This line in your here doc:
tell the "$POSITION_STRING" tab of its first window
…expands to (for example) this:
tell the "first" tab of its first window
…which is not syntactically valid. Remove the quotes around $POSITION_STRING on that line and it should work. However:
As #foo says, AppleScript understands numeric indexes just fine; there is no need to use English word indexes. Use tab $POSITION instead and it will work with any number, not just one through ten, and your script will be shorter besides.
Again echoing #foo, trying to substitute variables into a script text is usually a losing game. (For integers you can get away with it, but it becomes problematic for strings because quoting.) Make your script take arguments of its own and then pass them to osascript(1) — the man page shows how to do this. Note that arguments will always come in as strings, so you will need to coerce them if you need a number:
/usr/bin/osascript - "$POSITION" <<'EOF'
on run argv -- argv is a list of text
set n to item 1 of argv as integer
tell application "Google Chrome"
tell tab n of window 1 …
If your bash script is just turning around and calling osascript(1), you can go one step further: osascript functions perfectly well as an Unix interpreter, so you could rewrite your script like this:
#!/usr/bin/osascript
on run argv
set n to item 1 of argv as integer
tell application "Google Chrome"
tell tab n of window 1 …
Save that in a file by itself, mark it executable, and Bob's your uncle. (Unless you needed a bash function, in which case a here doc is a reasonable choice.)
osascript -e can accept newlines and variables. There may be reasons to use HEREDOCs, but I'm not aware of them and I find this a bit easier to work with.
Example:
TEST='Hello, World!'
osascript -e '
on run argv
display dialog item 1 of argv
end run
' $TEST

Replace text in an applescript using an applescript

I have several hundred lengthy applescripts to edit where I need to find and replace the following code snippet in various places in each script.
tell application "Adobe Photoshop CC 2015.5"
set myLayer to current layer of current document
if last character of mySport is "s" then
set contents of text object of myLayer to mySport & ""
else
set contents of text object of myLayer to mySport & "'s"
end if
end tell
I want to replace it with
tell application "Adobe Photoshop CC 2015.5"
set myLayer to current layer of current document
set contents of text object of myLayer to mySport & "'s"
end tell
Is there a way to write an applescript to find and replace several lines?
code screen grab
The second problem is how do I deal with the apostrophe contained inside the quotes?
You can probably tell that I'm an artist and not a developer or scripter! I tried to get an answer a while back but unsuccessfully and the problem is now become critical.
Many thanks in anticipation of an answer.
The best would have been to set this subroutine as a separate script library and call it it in each of your scripts. Doing so, only one change would be enough. I advice you to do this way for next time.
I dig to find a way to make change in a script, but that's not that easy. Script Editor as very limited capability for scripting. the work around is to use the GUI scripting, which means that any changes made by Apple in future versions may no longer work.
The script bellow simulate your keyboard action to search & replace CurString to NewString :
set myScript to "Users:imac27:Desktop:Testscript.scpt" -- path to your script
set CurString to "Set B to 2"
set NewString to "Set X to 5"
tell application "Script Editor"
open myScript
activate myScript
delay 2
tell application "System Events"
keystroke "f" using {option down, command down} --mode search & replace
keystroke tab using {shift down} -- got to search area
keystroke CurString -- set the search target
keystroke tab -- goto replace area
keystroke NewString -- set replace value
-- click on menu "Replace all " which is the 7th item of "Search" menu item (=item 14th of menu "Edit")
tell process "Script Editor" to click menu item 7 of menu of menu item 14 of menu 4 of menu bar 1
end tell
compile front document
save front document
close front document
end tell
This script opens the script, it does the search, replaces, clicks on "replace" menu, then it compiles new version, saves it and closes it. If you have many scripts, you must run it through a loop for each script.
I tested it OK with simple line : replace "Set B to 2" by new line "Set X to 5".
However, your issue is more complex because you want to replace several lines, not only 1. I did not found a way to set the search area with multiple lines. I tried with CR (13) or LF (10), but it does not work. May be someone has an idea for that part ?
Also, if you want to add a " in your search or replace patterns, you can use the following :
set Guil to ASCII character 34
Set CurString to "this is a " & Guil & "s" & Guil & " between quotes"
In this case, the CurString value will be : this is a "s" between quotes
I purchased Script Debugger from Late Night Software and it enables the script to access pieces of code and replace them. Mark Alldritt was amazing in the support he offered and the software is now my "first use" destination.
You are sure of your original script and the final script? In this case no hesitation to use xxd and sed below in hexadecimal script which you wrote you can test this script, no danger for your script. Naturally, you change your path and names at your convenience.
set thePath to POSIX path of (choose file)
tell application "Script Editor"
set doc to open thePath
save doc as "text" in POSIX file "/Users/yourname/Desktop/yourscriptold.txt"
close thePath
end tell
set scp to do shell script "xxd -p -c 100000 /Users/yourname/Desktop/yourscriptold.txt " & " | sed -e 's#74656c6c206170706c69636174696f6e202241646f62652050686f746f73686f7020434320323031352e35220a736574206d794c6179657220746f2063757272656e74206c61796572206f662063757272656e7420646f63756d656e740a6966206c61737420636861726163746572206f66206d7953706f727420697320227322207468656e0a73657420636f6e74656e7473206f662074657874206f626a656374206f66206d794c6179657220746f206d7953706f727420262022220a656c73650a73657420636f6e74656e7473206f662074657874206f626a656374206f66206d794c6179657220746f206d7953706f7274202620222773220a656e642069660a656e642074656c6c#74656c6c206170706c69636174696f6e202241646f62652050686f746f73686f7020434320323031352e35220a736574206d794c6179657220746f2063757272656e74206c61796572206f662063757272656e7420646f63756d656e740a73657420636f6e74656e7473206f662074657874206f626a656374206f66206d794c6179657220746f206d7953706f7274202620222773220a656e642074656c6c#' > /Users/yourname/Desktop/yourscriptnew.txt"
set scp to do shell script "xxd -r -p /Users/yourname/Desktop/yourscriptnew.txt >/Users/yourname/Desktop/yournewscript.txt"
do shell script "osacompile -o " & "/Users/yourname/Desktop/temporyname.scpt" & " /Users/yourname/Desktop/yournewscript.txt"
do shell script "rm -f /Users/yourname/Desktop/yourscriptold.txt "
do shell script "rm -f /Users/yourname/Desktop/yourscriptnew.txt "
do shell script "rm -f /Users/yourname/Desktop/yournewscript.txt "

Invoke copy & paste commands from terminal

Is is possible to invoke a copy command (as if the user pressed Cmd+C) from a bash script? Basically I want to write a simple script that I run with a global hotkey and it should take the current selection from the active app, replace something and paste the result. Is this possible?
The best I could come up so far is using pbpaste and pbcopy, but I'd like to automate that if possible.
If you're just trying to modify a text selection, you could use AppleScript.
osascript -e 'try
set old to the clipboard
end try
try
delay 0.3
tell application "System Events" to keystroke "c" using command down
delay 0.2
set text item delimiters to linefeed
set input to (paragraphs of (the clipboard as text)) as text
set the clipboard to do shell script "shopt -u xpg_echo; echo -n " & quoted form of input & " | rev" without altering line endings
tell application "System Events" to keystroke "v" using command down
delay 0.05
end try
try
set the clipboard to old
end try'
The first delay is for releasing modifier keys if the script is run with a shortcut that has other modifier keys than command. The second delay could also be reduced to something like 0.05, but long selections or for example web views often need a longer delay. Without the third delay, the clipboard would sometimes be set to old before the text would get pasted.
the clipboard as text and do shell script convert line endings to carriage returns by default. shopt -u xpg_echo is needed because the echo in sh interprets backslashes inside single quotes by default. If the input is longer than getconf ARG_MAX bytes, you can't use echo and have to either write it to a temporary file or use pbpaste.
pbpaste and pbcopy replace non-ASCII characters with question marks by default in the environment used by do shell script You can prevent that by setting LC_CTYPE to UTF-8.
Telling System Events to click menu bar items would often be even slower, and it wouldn't work in applications that don't have a menu bar or in full screen windows.
Another option would be to create an Automator service. But they also have small delays before they are run. There's a bug where the shortcuts for services don't always work until the services menu has been shown once on the menu bar. And the services aren't available when the frontmost application doesn't have a menu bar or a services menu.

osascript / syntax error: Expected end of line but found command name. (-2741)

I'm running into problems with a shell script that utilizes a small portion of Applescript. When I compile it with Applescript editor it works. It does not though within a shell script.
44:49: syntax error: Expected end of line but found command name. (-2741)
23:28: syntax error: Expected end of line but found “after”. (-2741)
Here is the shell code:
osascript -e 'tell application "System Events" -e 'activate'
osascript -e 'tell process "Application 10.5" -e 'set frontmost to true' -e 'end tell'
osascript -e 'delay 1' -e 'keystroke return' -e 'delay 1' -e 'keystroke return'
end tell
Applescript (that works):
tell application "System Events"
activate
tell process "Application 10.5"
set frontmost to true
end tell
delay 1
keystroke return
delay 1
keystroke return
end tell
[updated] / [solved]
This took care of any kind of problems I was having trying to modify the applescript to work within a shell script:
## shell script code
echo "shell script code"
echo "shell script code"
## applescript code
osascript <<EOF
tell application "Scriptable Text Editor"
make new window
activate
set contents of window 1 to "Hello World!" & return
end tell
EOF
## resume shell script...
It's very cool that you're able to put pure applescript directly into a shell script. ;-)
Each osascript(1) command is completely separate process, and therefore a completely separate script, so you can’t use state (such as variables) between them. You can build a multi-line script in osascript using multiple -e options -- they all get concatenated with line breaks between them to form the script. For a sufficiently long script, a separate file or a “here document”, as you used in your eventual solution, is a good way to go.
Also, if your script is mostly (or entirely!) AppleScript, you can make a “shell” script that simply is AppleScript using a shebang file that invokes osascript:
#!/usr/bin/osascript
display dialog "hello world"
...and then use do shell script as necessary.
Instead of using the -e flag, you can simply store your applescript code in a small text file and call
osascript /path/to/script
Also, if you're telling an application or process to do just one thing, you can write it like this:
tell process "MyProcess" to perform action.
Now that I think about it, running each line separately with the -e flag probably won't work because I don't think all the lines will connect and run as one program. For example, I just tested using osascript -e to set a variable. I then used a separate osascript -e to read the variable, and it couldn't.
[*]

Resources