跳转到内容

AppleScript 与 JXA:驱动没有命令行接口的应用 ​

很多 Mac 应用没有命令行接口,但支持被脚本控制——邮件、备忘录、访达、音乐、大部分办公软件都是。这套机制叫 Apple 事件,可以用 AppleScript 写,也可以用 JavaScript 写(后者叫 JXA,JavaScript for Automation)。

这一篇的核心不是语法,而是怎么查清某个应用到底支持什么。不会查,就只能到处抄别人的脚本,遇到版本变化无从下手。

开始之前 ​

需要能在终端执行命令。本文的示例都是只读操作,不修改任何数据。

先理解一件事:能不能控制一个应用,完全取决于开发者有没有提供接口。Apple 自家应用支持得较好,第三方差异很大。抄来的脚本跑不通,第一件事是确认这个应用是否真的支持你要做的操作,而不是反复调语法。

先查脚本字典 ​

每个支持脚本的应用都自带一份「脚本字典」,列出它能被控制的对象和操作。这是唯一可靠的依据。

打开方式:启动「脚本编辑器」(在「应用程序 → 实用工具」里,或用聚焦搜索),选「文件 → 打开词典」,从列表里选一个应用。

以「备忘录」为例,字典里能看到 note(便签)、folder(文件夹)这些对象,以及每个对象的属性,例如 name、body、creation date。脚本里能用的词就是这些,字典里没有的写了也不认。

应用不在列表里,说明它不支持脚本控制。这种情况下只能考虑辅助功能层面的模拟操作(例如用 Hammerspoon 发送按键),那是另一条路,可靠性低得多。

从终端执行 ​

sh
osascript -e 'tell application "Finder" to get the name of the front window'

osascript 是执行 AppleScript 和 JXA 的命令行工具。-e 表示后面跟的是要执行的脚本文本。tell application "..." to ... 是 AppleScript 的基本结构:告诉某个应用做某件事。

用单引号包住整段脚本,因为里面有双引号。访达没有打开窗口时这条命令会报错,这是预期行为。

写成多行脚本时存成 .applescript 文件再执行:

sh
osascript ~/bin/my-script.applescript

JXA:同样的能力,JavaScript 的语法 ​

同一件事用 JXA 写:

sh
osascript -l JavaScript -e 'Application("Finder").windows[0].name()'

-l JavaScript 指定语言,不加时默认是 AppleScript。

怎么选:熟悉 JavaScript 就用 JXA,否则用 AppleScript。两者能力相同,但网上的示例绝大多数是 AppleScript——遇到问题时能搜到的答案多得多。这个现实因素比语法偏好更重要。

JXA 的另一个问题是文档稀少,Apple 对它的更新也不积极。作为长期方案,AppleScript 目前更稳妥。

一个有用的例子 ​

把当前浏览器标签页的标题和网址取出来,这是做笔记时的常见需求:

sh
osascript -e 'tell application "Safari" to get {name, URL} of current tab of front window'

{name, URL} 表示同时取两个属性,返回一个列表。Safari 没有打开窗口时会报错。

换成 Chrome 时,词不一样:Chrome 的字典里用 active tab 而不是 current tab。这正是必须查字典的原因——同类应用的术语不通用。

自动化授权 ​

第一次让 A 脚本控制 B 应用时,系统会弹出授权请求:「xxx 想要控制 xxx」。允许之后,授权记录在「系统设置 → 隐私与安全性 → 自动化」里,可以随时撤销。

有两点容易困惑:

授权是按「谁控制谁」这一对关系记录的。 终端控制访达、终端控制备忘录是两条独立授权。

从 launchd 运行时,请求者是实际执行的程序。 定时任务里执行 osascript,系统看到的请求者可能是 /bin/bash 或 osascript 本身,而不是你的脚本文件。所以后台任务第一次运行时可能因为没人点「允许」而静默失败——先在终端手工跑一次完成授权,再交给 launchd。

详细机制见TCC 隐私授权。

检查结果 ​

三条命令验证环境正常:

sh
osascript -e 'return 1 + 1'
osascript -l JavaScript -e '1 + 1'
osascript -e 'tell application "System Events" to get name of every process whose background only is false'

前两条应当都输出 2,证明两种语言都能执行。第三条列出当前所有非后台进程的名称,会触发一次自动化授权请求——允许之后再执行就不会再问。

「系统事件」(System Events)是个特殊的辅助应用,很多跨应用操作都要经过它。它出现在自动化授权列表里是正常的。

动手练习 ​

用只读操作熟悉查字典的流程,不修改任何数据:

  1. 在脚本编辑器里打开「备忘录」的词典,找出 note 对象有哪些属性。
  2. 写一条命令,取出备忘录中便签的数量。提示:count of notes。
  3. 在「系统设置 → 隐私与安全性 → 自动化」里找到刚产生的授权记录。
  4. 关掉那条授权,再跑一次第 2 步,观察报错。
  5. 重新打开授权,确认恢复正常。

成功标准是第 4 步你看到的是授权被拒的错误,而不是语法错误——能区分这两类失败,排查效率会完全不同。全过程没有写入或删除任何便签。

返回自动化与脚本