article_image

通过 Automator 的自带模块,我们已经能够熟练组合出动作,并且对“编程”的概念也有了一定的了解。到了这个阶段,我们会遇到自带模块不够用的瓶颈:

  • 不能解压 RAR 文件,解压 Zip 总出现乱码1
  • 不能快速把表格导出成图片等格式
  • 不支持正则表达式
  • ……

**终于到了学习脚本的时候。**应用本身没有提供的功能,我们自己来实现。

说到脚本,不懂编程的读者可能会感到一些压力。事实上,很多简单的脚本已经足以发挥大作用,比如第一章中提到的 Automator 解压工具,内部的脚本只有一句话:

Alt text
解压动作的脚本只有一句话

本系列文章中所涉及的脚本,多数不会过于复杂,文章也会提供大量可以直接套用的模板代码,让你快速上手。即使你暂时没有弄懂脚本的原理,也可以下载现成的 Automator 动作来使用。

Automator 支持的脚本语言有 AppleScript、Shell、Python、JavaScript……我将在接下来的章节中重点介绍性价比最高的 AppleScript 和 Shell。本章会着重讲 AppleScript。

考虑到在暗色模式下,AppleScript 脚本编辑界面会呈现黑白相间的熊猫配色,且存在高亮适配问题,文中有必要呈现代码的地方都采用浅色主题以清晰呈现。

为什么是 AppleScript

这一章我们先来学习 AppleScript。稍微了解 AppleScript 的人,喜欢说它“简单得和说话一样”,这话不假;不过 AppleScript 真正厉害的地方,还是在于能够直接和 macOS 系统交互

  1. 能直接控制系统控件,如通知、输入框。
  2. 能直接模拟日常操作,如鼠标点击、按键点按。

这些特性,恰好补足了 Automator 缺少的功能。下面我们会从 AppleScript 的语法结构开始熟悉这门语言,再一一讲解如何结合 AppleScript 和 Automator 来和 macOS 沟通。

注:Automator 中的 JavaScript 全称“JavaScript for Automation”,可以理解为套用了 JavaScript 语法格式的 AppleScript,并不具备完整的 JavaScript 功能,本系列文章中不作介绍。

AppleScript 的语法结构

如果你接触过别的编程语言,AppleScript 给你的最大感受应该就是简单直白,它几乎没有特殊符号和单词,里面出现的都是最日常的英文单词。编写 AppleScript 就像用英语和 macOS 对话:

Alt text
最简单的 AppleScript 脚本

通过这段简单的 AppleScript 脚本就能绕开繁琐的菜单,直接让 QuickTime 开始录制屏幕。我们再来看一个比较复杂的脚本:

Alt text
稍微复杂一些的 AppleScript 脚本

第二个脚本的用途是从表格快速导出图片。虽然形式上看起来的复杂了不少,但两个脚本有一点都是相同的:采用了类似俄罗斯套娃的结构。其实,AppleScript 最基本的工作方式就是通过一连串的 tell 语句,把命令传达到最后需要执行的动作上

tell application "应用名"
    tell 部件A
        搞点小动作
    end tell
    tell 部件B
        搞点小动作
    end tell
end tell

关于 AppleScript 的更多复杂语法结构,这里就暂不展开,用到时我们再见招拆招。且让我们快马加鞭进入和 Automator 相关的部分。

用编辑模块的方式来写 AppleScript 代码

和组合任意一个普通 Automator 动作一样,编写 AppleScript 代码我们也可以使用“模块”——模板代码——来降低上手难度

我们随便抽取一个 Automator 动作(它的作用是用正则表达式进行文本替换),里面的代码长这样:

Alt text
未标注过的 AppleScript 代码

相信没有代码基础的读者,第一眼是看不懂的。但是给每一部分标上各自的用途,原先的代码就变得很好理解:

Alt text
标注过的 AppleScript 代码

每个被划出来的部分都可以视为一段模板代码,各自对应一个功能,就像 Automator 里的模块一样可以自由组合。我们真正需要修改的只有个别参数,完全不用从零开始码代码。接下来我们将看到大量的模板代码,它们涵盖了 macOS 自动化中的常见的操作,能够补足 Automator 所不具备的功能。

顺便说一句,上面那个脚本差不多是本系列文章里面最复杂的。曾经沧海难为水,接下来的脚本都没什么好怕的。

将 AppleScript 嵌入 Automator

将 AppleScript 引入 Automator,往往是为了拓展后者处理文本和文件的能力,所以和任何一个普通模块一样,运行 AppleScript 的模块也有输入和输出

将“运行 AppleScript”加入编辑界面,我们看到代码框中已经有了几行代码。它们的用处就是让 AppleScript 接受上一步的结果作为输入,并返回一个新的结果当作输出。注意,如果你新建的 Automator 动作是 Application 格式,那就大可将“运行 AppleScript”放在第一步,Automator 会自动将托拽到它图标上的文件/文件夹作为输入传给 AppleScript。

Alt text
Automator 中的 AppleScript 模板

其中第一行的 input 就表示接受输入,没事儿我们不会改它;return input 则表示返回一个数据作为整个步骤的输出,不过我们辛苦了半天,又何苦把输入原样输出来2?实际编写脚本的时候,我们往往会在 Your script goes here 部分设置一个新变量 名字随你取 并作为输出(return 名字随你取)。

当然,并不是所有时候都需要输出,比如我只想通过 AppleScript 来发一条通知,后续没别的步骤,那么 return XXX 部分也就不需要了。有时整个 on run 都可以不要,后面我们会看到一个“导出表格图片”的动作,它并不涉及 Automator 内部的输入/输出,只是披上 Automator 这层壳子以便于使用。

Alt text
仅仅发送通知,就不需要设置输出

一般来说,需要处理的只是一段文本或者一个文件时,Automator 给你提供的样板代码已经足够。

赋予 AppleScript 批处理能力

等等,看到这里,想必你会问:那我想批量处理一堆文件该怎么办?

我们只需要对代码再进行一些修改:

on run {input, parameters}
    repeat with 每个文件 in input
        核心代码(实际处理文件)
    end repeat
end run

第 2、4 行组成了一个循环处理系统,可以让 Automator 挨个处理一堆文件。我们编写 AppleScript 脚本时说不好它以后会不会用在批处理上,所以我的习惯是只要和文件处理相关,都在核心代码前后套用上面的两句话

下面这个“锁定文件”的 Automator 动作就具有批量处理的能力,可以用来一次性锁定多个重要文件/文件夹,避免清理桌面的时候误删它们。

Alt text
通过 Automator Application 锁定文件

AppleScript 独特的文件路径

AppleScript 有一个很“蠢”的特点,就是认不出直接输入的文件路径。所以在多数涉及文件处理的 AppleScript 脚本中,你都能看到这样两行代码:

set newPath to POSIX path of thePath
set newAlias to POSIX file newPath as alias

**它们的作用是先把输入的文件路径转为 POSIX path 格式,再转成 Alias 格式,以便 AppleScript 识别。**在上一节锁定/解锁的文件的动作里,就有这样两行负责转换格式的代码。

Alt text
转换路径格式的代码模板

的确,AppleScript 的语法在简洁性上有欠缺。不过换个角度看。那些看似长长的 AppleScript 脚本里面,其实也包含了不少无需改动的路径格式转换模板,真正需要我们自己编写和修改的地方着实不多。

另外告诉你一个好消息,下一章学用 shell 处理文件时完全用不到麻烦的路径转换;而且在文件处理中,shell 的使用率也比 AppleScript 更高。不过在此之前,我们还是打好 AppleScript 的底子。

带输入的提示框:随时随地快速输入文章

通过前面几组模板,我们已经知道如何用 AppleScript 来处理文件和文本。但如果需要中途输入文本,该怎样实现?这个需求并不少见,用正则表达式重命名文件、快速添加提醒事项时都需要临时输入文本。

Automator 里自带了一个“请求文本”的模块,但它并不能插在到 AppleScript 中间。其实“请求文本”是一个提示框(Prompt)控件,我们用 AppleScript 也能写一个出来,而且可以高度自定义其中的内容。下面通过一个“快速添加提醒事项”的动作来熟悉提示框的 AppleScript 代码模板。

Alt text
快速添加提醒事项

> 动作下载 🔗

需要事先说明一下,Automator 自带了添加提醒事项的模块,所以这个动作在实用性上优势不大,不推荐日常使用。好在它涉及的 AppleScript 脚本简单明了,更适合拿来讲解提示框的用法。

Alt text
带输入的提示框语法模板

从中可以提炼出提示框的模板:

display dialog "提示语" default answer "默认回答"

其中 提示语默认回答 部分都可以自己填写。以后我们还会讲到一个上传文件到 GitHub 的动作,里面提示框的默认回答是 by Minja,这样我就不用每次通过手打告诉大家“这份文件是我上传的”。

知道怎样创建一个带输入的提示框之后,我们再来看看怎样获得刚刚输入的文本。下面是提炼出的第二个模板:

set new_variable to text returned of result

通过这样一条简单的 set 句子,我们就得到了提示框中的文本。注意,new_variable 部分你可以换上其他喜欢的名字,只是不能用中文,AppleScript 无法识别。

一句 display、一句 set,我们就能够在 AppleScript 中使用临时输入的文本。

带选择的提示框:实现 if 判断

一下子看了那么多内容,是不是有点累?告诉你一个好消息,我们刚刚已经学完了提示框的高级玩法,这一节的内容轻松多了。

提示框除了提醒你输入文本,还可以请求用户做选择;根据选择结果的不同,AppleScript 还能执行不同的操作。不少人会同时使用多个浏览器,下面这个快速操作就能让你在各个浏览器之间灵活切换。

> 动作下载(快速操作)🔗

运行快速操作后,点击不同的按钮,就可以在对应的浏览器里面打开链接。

查看里面的代码,其实这就是一组 if 判断,根据按钮的值触发不同的操作。这种带有多个按钮的提示框,我们可以自己来做:

display dialog the ["标题名"] buttons {"按钮1", "按钮2","按钮3"}
Alt text
AppleScript 代码和提示框按钮的对应关系

标题名 和各个 按钮 的名字都可以自己取,中英文不限。理论上你可以添加更多的按钮,我也无从得知上限在哪。

提示框很快就做好了,接着才是重头戏:获取按钮的返回值

set new_variable to button returned of result

在设置提示框的语句下面紧接着加上这句话,就能获得按钮的值了(new_variable 的名字可以自己设置,需要英文)。按钮返回值即按钮上的文本。

列表框:实现更灵活的选择

提示框简单易用,不过里面的按钮都是预设的,如果我们想从当前网页、当前运行的程序等变化的项目之中进行选择,就要请出列表框

列表框比提示框更加灵活,既能显示预设的项目,也可以提供实时项目供选择,而且可以容纳更多的项目(实在很难想象在几十个按钮之间点选的画面)

Alt text
列表可以显示更多、更灵活的选项

列表框平时我们接触得比较少,怎么创建、怎么添加、怎么选择……都是问题。好在不少 AppleScript 脚本都能够帮忙自动生成列表,不用完全手写;所以,我们先从最关键也是最不可省略的选择项目这一步入手。

**来看一个“关闭后台进程”的动作,它在运行后会弹出一个列表,显示当前在运行的后台进程,你可以选中一个或多个并结束它们。**这个动作还会默认选中 Finder 进程,因为它是日常“死亡率”最高的进程之一,经常被我们用终端 kill 掉重启……玩腻了终端和活动检测器,可以试试这个轻量直观的“进程杀手”。

Alt text
显示当前的后台进程

> 动作下载 🔗

“选择进程”这个功能就是靠列表框来实现的,刚好这个动作把列表框的常见自定义参数都用上了,我们可以从中提炼一下列表框的语法模板

Alt text
列表框的语法模板
  • 列表名称(选填)with title "列表名"
  • 默认选项(选填,默认不选)default items "默认选项名"
  • 启用多选(选填,默认单选)with multiple selections allowed <br>

是否启用多选,具体要看你的使用场景,本案例动作中我们需要一次性关闭多个进程(比如 iCloud 同步延迟,就要同时重启 cloudd、CloudD 等多个进程),所以我启用了多选。稍后还有个“从 Safari 拷贝链接”的动作,一次只拷贝一个 URL,就不需要多选。

选择语句的重要知识点就这些了。对着上面的模板,你能看出来“关闭后台进程”脚本对应的列表框部分吗?(答案在文末)

Alt text
代码对应列表框的哪个部分?

搞明白最关键的选取语法后,我们再试试从零开始自己创建并完善一个列表。这回步骤稍微多一点,但是想玩转自定义列表,这点投资还是值得的。代码主要包括 3 个步骤:

  1. 创建列表:set 列表名 to {} 
  2. 添加项目:set end of 列表名 to 想要添加的变量
  3. 选择项目:choose from 列表名

听起来比较抽象,我们代到案例里面就比较好理解。

下面是一个“从 Safari 拷贝链接”的动作(原作者 Dr.Drang),可以快速把链接粘到任意输入框里,避免频繁去浏览器里手动复制。

Alt text
从 Safari 拷贝链接

动作的核心就是一段 AppleScript 脚本,比较长,但把列表相关的部分抽出来并不难:

Alt text
列表相关的代码

动作的工作原理就是创建两个空列表,把 Safari 标签页的名字和 URL 分别填进去,然后让你从列表里选自己要的链接(界面上显示的是网页名,返回的是 URL,这也是要创建两个列表的目的)。

和刚才结束后台进程的动作类似,你可以自定义拷贝链接动作的标题和默认选项(本文提供的动通过 default items frontName 默认选中当前页面的 URL)。

快速操作下载:

模拟鼠标点击:精确点击 macOS 中的任何元素

上面几个添加提醒事项、获取网页链接的例子里,最核心的、真正干事情的代码都只有几行,它们来自各个应用的 AppleScript suite。你可以在 Script Editor(脚本编辑器)里通过快捷键 ⇧Shift - ⌘Command - O 打开 AppleScript 词典,找到自己需要的 suite。

但是,有些应用 suite 太复杂,有些根本不提供 suite,那怎么和这些应用沟通?着就要靠 GUI Scripting,即通过脚本来模拟键盘和鼠标操作

没有 siute、听不懂 tell something 都没关系,只要有 GUI Scripting,我们就能提溜着应用、手把手教它完成任务。不管这个应用多“笨”或者多不听话,都可以通过脚本来控制它。

这一节先说模拟鼠标点击 UI 元素。

模拟点击是一瓶万金油,不过也有小瑕疵。通过 AppleScript 模拟点击时我们不能随意移动鼠标,否则脚本就会找不到菜单项。这有点像学开车,不幸碰上一个总在副驾上狂踩刹车喊停的教练,你指定开不好。

总之,GUI Scripting 更适合作为万能备用方案。

模拟键盘操作:用 AppleScript 实现快捷键

模拟快捷键是 GUI Scripting 的另一大应用场景。相比模拟鼠标点击,快捷键不需要考虑 UI 元素,所以脚本编写起来轻松得多。

GUI Scripting 模拟键盘操作有两种方法,分别是:

  1. 根据肉眼所见的按键值来点击keystroke "按键值"3,所见即所得,很容易理解,大众方案。
  2. 根据按键所对应编码来点击key code 键位编码,编码和键位对应,指哪打哪不会搞混,外接键盘、自己魔改键盘的读者更适合用这个方案。

**我们一般使用 keystroke 方案就好。**AppleScript 不仅可以模拟单个按键,也能模拟一组快捷键,完整的代码模板是:

tell application "System Events"
    tell application "目标应用" to activate
    keystroke "按键值" using {修饰键}
end tell

其中目标应用按键值修饰键 需要我们自己填写:

  1. 目标应用:可以不填,试运行的时候 AppleScript 会让你选到底要激活哪个应用。
  2. 按键值:就是键帽上的英文或数字,比如 n1
  3. 修饰键:用于组合按键的特殊键,比如 command downshift down,按键名后面的 down 表示“按下去”。可以用英文花括号 {} 一次性把多个修饰键括起来,比如:<br>{command down, shift down}

来实操一下,现在想在预览工具里模拟“新建”这个操作,就可以用下面的代码:

tell application "System Events"
    tell application "Preview" to activate
    keystroke "n" using {command down}
end tell

在“从表格快速导出图片”这个动作里,就两次用到了 keystroke 语句。这个动作可以把 Numbers 或 Excel 中表格/图标快速保存成图片并放到桌面上,它的运行步骤是:

  1. 请求用户输入新文件名(前几节的内容,这里用上了)
  2. 激活预览工具,模拟快捷键 ⌘Command - N 把表格粘上
  3. 保存刚刚新建的图片
  4. 迷你快捷键 ⌘Command - Q,退出预览工具
Alt text
从表格导出图片

用 Automator 打开动作,可以看到里面两次模拟了快捷键(红色部分)。这个动作本身没有太复杂的地方,只是把我们日常保存图片的流程用脚本模拟了一遍,但已经能够省下不少时间。

Alt text
在 AppleScript 中模拟快捷键

使用 Automator(以及其他任何自动化方法)控制键盘时,都需要先把对应的应用/动作手动拖进4到 Accessibility 白名单。出于安全考虑,我建议每次用到一个功能就制作一个 Application 动作加进白名单,因为 macOS Mojave 以应用为单位进行授权,直接赋予应用较高的权限会很危险,而 Automator Application 毕竟是自制、“开源”的,还处于我们的控制范围之内。

Alt text
模拟快捷键之前需要把动作添加到白名单

模拟快捷键的脚本非常“懒人”,但足够高效,即便不知道 suite、也搞不清 UI 元素,只要某个功能被绑上了快捷键(你可以在系统设置里自己绑),我们就能通过 GUI Scripting 来启用它

顺予指出,下一章的主角——Shell 脚本——也会经常借用 GUI Scripting 来模拟快捷键。所以,即使你的主力脚本语言不是 AppleScript,也值得学一下 GUI Scripting 相关的内容。

注:想使用键位编码方案的读者,这里推荐一个编码速查的小工具:Full Key Codes。关于两种方案的更多区别,可以看这篇文章 《Doug's AppleScripts for iTunes » System Events, Key Code and Keystroke》。

发送通知

运行一段 AppleScript 脚本后,得让 Automator 告诉我们脚本是否运行结束、是否执行成功,**系统通知是简单好用的提醒方式。**丰俭由人,你可以使用 Automator 自带的通知模块,也能通过 AppleScript 脚本进行深度自定义。

先来看简易版的。直接把 AppleScript 结果设为变量,然后在系统通知模块中调用,就完成了基础版的通知。

Alt text
通过系统通知模块显示 AppleScript 脚本运行结果

但是这个工作流只能告诉你运行结果,想要下面这些高级功能,就得比一般人多学一点:

  • 控制通知的提示音
  • 根据运行的成败发布不同提示
  • 在通知中灵活调用任何脚本中出现过的变量
  • ……

以一个“校验文件 MD5 值”的 Automator 动作为例,把上面的高级特性一网打尽。借助脚本,Automator 校验成功/识别后就可以发出不同的通知,而且能够显示文件的校验结果。通知里还可以填入不同的 emoji 表情,让结果看起来更加直观。

Alt text
校对是否成功,通知内容不同

通过通知的提示,我们就能知道文件有没有被篡改:如果校验出来的 MD5 值和从下载站点复制下来的不一样,就说明原文件或者你家 Wi-Fi 被人动了手脚。这不是危言耸听,请自行搜索关键词 运营商劫持

除了 MD5,有的站点还提供别的校验方式,对应的 Automator 动作一并提供如下:

言归正传,我们把焦点移回 AppleScript 上来,看看怎么实现这种聪明的通知。

先从一条完整的系统通知模板入手:

display notification "All graphics have been converted." with title "My Graphic Processing Script" subtitle "Processing is complete." sound name "Frog"

分解开来看的话,通知和脚本的对应关系是:

  1. 标题(必选)with title "标题"
  2. 副标题(可选)subtitle "副标题"
  3. 信息(可选)"信息"
  4. 提示音(可选)sound name "Frog"
Alt text
代码和通知的对应关系

实际使用时,除了标题是必选项,其他通知内容都可以自由取舍。

回到刚才校验的文件的脚本上,可以看到其中负责发送通知的代码被嵌进了一组 if 判断中,这表示校验成功就发送第一条通知,反之发送第二条通知。

Alt text
负责发送通知的代码

两条通知的结构是一样的,都是“标题+信息+提示音”,不含副标题。校验文件动作的代码和通知也能一一对应起来:

Alt text
Group

从上图可以看出 md5 对应了 854e452f7fa87147e1522b578ae51ca0,这就是通过 AppleScript 来发送通知的一个优势:可以自由显示脚本中出现过的变量md5 这个变量在之前已经设置过,它的值就是实际检验出来的 MD5 值)。如果想要在通知里显示脚本运行的结果,AppleScript 是一个万能的工具。

补充包:自定义通知提示音

讲了一堆文本方面的内容,我们最后提一下提示音的自定义。除了默认提示音 Frog,你还可以在 /System/Library/Sounds/ 文件夹中看到其他的音频文件。把 sound name "Frog" 中的 Frog 改成其中任意文件的名称,就能换上对应的提示音。

Alt text
自定义提示音

练习答案

Alt text
代码对应列表框的各个部分

  1. 下一章来解决这个问题。
  2. 严格来说,input 这个变量当然可以有所改动,这里是为了幽默而取了不那么严谨的表达。
  3. 按键值还可以是中文,但不在本节讨论范围内。
  4. 必须手动拖进去,根据系统提示来操作是无效的。

author_avatar

Lawyer, macOS/iOS Automation Amateur