最近开通了我的技术博客,不定期更新一些学习和感受,目前主要是iOS,明年预期会更新一些Web端以及Flutter的内容~
文档镇楼:AppleScript Language Guide
前言
笔者学习AppleScript的起因,是因为本人键盘修饰键的使用习惯和其他同事不同,笔者习惯于将"大小写修饰键"和"Ctrl键"互调位置,以至于习惯默认修饰键配置的同事在我的电脑上进行调试,总是忍不住感叹一声:
“这TM是人用的吗?”
相信了解iOS开发的小伙伴们都知道,Ctrl + Command + 上下,可以切换.h和.m文件,但是在笔者的键盘上,执行这一操作时,不但没有任何的效果,NM还会把键盘设置成了大写模式,在一次又一次,怒吼的咆哮后,笔者痛定思通,决心要解决这个问题,在如何不改变自己键盘使用习惯的前提下,让来调试代码的同事们也感到如使用自己键盘般的流畅和丝滑呢?于是,就有就有了这篇
聊一聊脚本语言:AppleScript
AppleScript的实践应用
实践:还原修饰键默认设置
本文下面比较详细地介绍了AppleScript基础知识,在学习它们之前,只需要简单了解AppleScript中常用的几个概念,就可以实现GUI Scripting的神奇功效,我们先来看看AppleScript中的常用概念
AppleScript的常用概念
这是一个神奇的传送门☞脚本编辑器(本文所有代码均可直接在IDE中执行)
我们先了解下我们的IDE工具,打开脚本编辑器
点击图片中的按钮,可以看到上面除了"结果"选项外,还多出了"信息"、"事件"和"回复"三个选项,在这里面可以看到更为详细的打印信息!
AppleScript的必须掌握的命令:
- tell someone do something
AppleScript的语法十分接近自然语言,想要操作一个对象执行某些操作,只需要使用"tell命令 +对象类型 + 对象名",在之后执行end tell
,结束当前的回话
激活终端
tell application "Terminal"
activate -- 告诉 终端,执行激活命令
end tell
关闭终端
tell application "Terminal"
quit -- 告诉 终端,执行退出命令
end tell
System Events是系统应用,我们有时寄期望于在系统应用中找到某个正在执行的进程(Process)
tell application "System Events"
tell process "Terminal"
end tell
end tell
在执行这段代码后,你会发现,其实什么都没有执行,不用慌,这是因为你并没有告诉"终端"要做些什么,那么我们通过entire contents
命令获取Terminal内的所有UI元素
#执行下面代码前,先激活"终端"
tell application "System Events"
tell process "Terminal"
entire contents --获取区域内所有的UI元素
end tell
end tell
执行后,会得到"终端"内的所有UI元素的完整描述
选择日志中的一条进行分析
button 1 of window "终端 — -zsh — 80×24" of application process "Terminal" of application "System Events"
其实这其中的从属关系已经十分明确了,翻译过来就是
应用"System Events"的应用进程"Terminal"的"窗口"的"按钮1"
- 模拟点击
button 1的描述,我们已经很清楚了,那button 1到底对应"终端"中的哪个按钮呢?其实单从名字上看,很难看出button 1对应的是哪个按钮,那么我们用一个很简单的方法,确定它的位置,让button 1执行点击操作,通过它的点击效果反推它的位置!
#执行下面代码前,先激活"终端"
tell application "System Events"
tell process "Terminal"
(*
注意:由于环境的差异性,直接复制这段代码到你的脚本编辑器,可能得不到想要的效果
这里一定要输入你上面entire contents后,打印的"button 1"的完整结果
*)
click button 1 of window "终端 — -zsh — 80×24" of application process "Terminal" of application "System Events"
end tell
end tell
执行上述操作后,发现"终端"被关闭了,原来button 1对应的就是"终端"左上角的关闭按钮
请注意:并不是所有的button 1对应的都是应用的关闭按钮,还要具体情况具体分析
- 模拟输入
我们打开了终端,想看当前目录下存有哪些文件,该如何操作呢?
想一下这个问题,我们都知道在终端中查看目录下文件的命令是ls
,那么就可以将这个问题拆分为键入'ls'和点击回车两个步骤
在AppleScript中,输入字符串的命令是keystroke
,你也可以用key code
实现单键点击
我们完善一下代码:
tell application "Terminal"
activate
end tell
tell application "System Events"
tell process "Terminal"
keystroke "ls"
delay 1 -- 延时一秒后执行
key code 36 -- 回车的键位码为36
end tell
end tell
完整的键位码传送门☞键位码
我们有时需要通过组合按键的方式执行某一操作,通过在键入命令后添加using {按键A down, 按键B down}
的方式,如:
#强制关闭应用快捷键
key code 53 using {command down, option down} -- Esc的键位码为53
- 执行shell命令
在终端中通过执行shell命令的方式可以达到和模拟键入的同样的效果
tell application "Terminal"
activate
do script "ls"
end tell
当然也可打开某路径下的文件,这里我们尝试打开"系统偏好设置"中的"键盘偏好设置"
tell application "Terminal"
activate
#打开键盘偏好设置
do script "open . '/System/Library/PreferencePanes/Keyboard.prefPane'"
end tell
我们简单回顾一下,现在我们已经了解了:
如何激活应用,并在当前的系统进程中找到它
如何获取某一区域内的全部UI控件,并尝试点击其中的某一个单独控件
如何实现模拟字符串输入、单键输入和组合输入
如何关闭应用
此外,我们再多了解两个帮助我们编写代码的两个小知识点:
1.注释
AppleScript中的注释分为单行注释和多行注释
单行注释:#
、--
多行注释 (* 中间的部分都是注释 *)
(上文中三种注释均有用到)
2.输出日志
log
命令,可以通过打印辅助信息,来帮助我们编写代码
至此,你已经掌握了实现恢复系统默认修饰键的全部基础概念,小伙伴们完全可以自己尝试写下代码
核心代码:
#激活
tell application "Terminal"
activate
#打开键盘偏好设置
do script "open . '/System/Library/PreferencePanes/Keyboard.prefPane'"
end tell
tell application "System Events"
tell process "系统偏好设置"
#点击修饰键
click button "修饰键…" of tab group 1 of window "键盘" -- of application process "System Preferences" of application "System Events"
#更改为默认状态
click button "修饰键…" of tab group 1 of window "键盘" -- of application process "System Preferences" of application "System Events"
click button "恢复成默认" of sheet 1 of window "键盘" -- of application process "System Preferences" of application "System Events"
click button "好" of sheet 1 of window "键盘" -- of application process "System Preferences" of ®application "System Events"
end tell
end tell
AppleScript基础知识
1.基本模块
语法
#符号'¬'(option+'L') 用来将语句延续到第二行
display dialog "This is just a test." buttons {"Great", "OK"} ¬
default button "OK" giving up after 3 --result:调出弹窗,默认键是OK,3秒后消失
变量和属性
#变量赋值
set myName to "John"
copy 22 to myAge
#局部变量
local windowCount
local agentName,agentNumber,agentHireDate
#全局变量
global gAgentCount
global gStatementDate,gNextAgentNumber
#属性
property defaultClientName : "Mary Smith"
#字符串中引用变量
set aPerson to "GCS"
display dialog "Hello " & aPerson & "!"
类型转换
set myText to 2 as text
运算符
3 * 7 - "1" --result 20
List(数组)
#初始化数组
set myList to {1, "what", 3} --result: {1, "what", 3}
set beginning of myList to 0 --首位设置为0
set end of myList to "four" --末位设置为"four"
set item 2 of myList to 4 --第二位设置为4
myList --result: {0, 4, "what", 3, "four"}
Record(键值对)
#存值
set myFullName to {firstName:"John", lastName:"Chapman"}
#取值
set myLastName to lastName of myFullName --result "Chapman"
2.控制语句
considering/ignoring
"Hello Bob" = "HelloBob" --result: false
ignoring white space --忽略空格
"Hello Bob" = "HelloBob" --result: true
end ignoring
"BOB" = "bob" --result: true
considering case --考虑大小写
"BOB" = "bob" --result: false
end considering
try-error
try
word 5 of "one two three"
on error
error "There are not enough words."
end try
if
set currentTemp to 10
if currentTemp < 60 then
set response to "It's a little chilly today."
else if currentTemp > 80 then
set response to "It's getting hotter today."
else
set response to "It's a nice day today."
end if
display dialog response
repeat-exit
set num to 0
repeat
-- perform operations
if num < 5 then
set num to num + 1
else
display dialog num
exit repeat
end if
end repeat
repeat (number) times
set x to 3
set power to 3
set returnVal to x
repeat power - 1 times
set returnVal to returnVal * x
end repeat
return returnVal
repeat while(当型循环)
set userNotDone to true
repeat while userNotDone
set userNotDone to enterDataRecord()
end repeat
on enterDataRecord()
delay 3
false
end enterDataRecord
repeat with loopVariable
数值循环
set n to 3
set returnVal to 0
repeat with i from 0 to n
set returnVal to returnVal + I
end repeat
return returnVal
(数组遍历)
set peopleList to {"Chris", "David", "Sal", "Ben"}
#方法一
repeat with aPerson in peopleList
display dialog "Hello " & aPerson & "!"
end repeat
#方法二
repeat with i from 1 to (number of items in peopleList)
display dialog "Hello " & item i of peopleList & "!"
end repeat
String to List
set wordList to words in "Where is the hammer?"
repeat with currentWord in wordList
log currentWord
if (contents of currentWord) is equal to "hammer" then
display dialog "I found the hammer!"
end if
end repeat
record遍历
#在文档中没有找到遍历record的方法,不知道是不是不小心遗漏了
#但是在Stack Overflow中看有如下方法,引入了OC中的Foundation库,实现了record的遍历
use framework "Foundation"
set testRecord to {a:"aaa", b:"bbb", c:"ccc"}
set objCDictionary to current application's NSDictionary's dictionaryWithDictionary:testRecord
set allKeys to objCDictionary's allKeys()
repeat with theKey in allKeys
log theKey as text
log (objCDictionary's valueForKey:theKey) as text
end repeat
3.函数构造
on greetClient(nameOfClient)
display dialog ("Hello " & nameOfClient & "!")
end greetClient
greetClient("GCS_DEVELOPER")
4.脚本对象
定义/执行脚本
script John
property HowManyTimes : 0
to sayHello to someone
set HowManyTimes to HowManyTimes + 1
return "Hello " & someone
end sayHello
end script
tell John to sayHello to "Herb"
#John's sayHello to "Jake" --result: "Hello Jake"
#sayHello of John to "Jake" --result: "Hello Jake"
初始化脚本
on makePoint(x, y)
script thePoint
property xCoordinate:x
property yCoordinate:y
end script
return thePoint
end makePoint
set myPoint to makePoint(10,20)
get xCoordinate of myPoint --result: 10
get yCoordinate of myPoint --result: 20
继承
script Alex
on sayHello()
return "Hello, " & getName()
end sayHello
on getName()
return "Alex"
end getName
end script
script AlexJunior
property parent : Alex
on getName()
return "Alex Jr"
end getName
end script
-- Sample calls to handlers in the script objects:
tell Alex to sayHello() --result: "Hello, Alex"
tell AlexJunior to sayHello() --result: "Hello, Alex Jr."
tell Alex to getName() --result: "Alex"
tell AlexJunior to getName() --result: "Alex Jr"
写在结尾
本文记录的只是AppleScript中的一小部分内容,笔者在网上暂时还没有找到十分详细的中文文档和介绍,所以想要更加深入的了解和学习AppleScript还是查看官方文档!
一手资料☞AppleScript Language Guide
参考资料:
手把手教你用 AppleScript 模拟鼠标键盘操作,实现 macOS 系统的自动化操作
AppleScript中的保留字