Mac 用户一定会经常重复的一个操作是:把来源各异的 dmg 文件挂载1 、zip 文件解压,把其中的 .app 文件拖到 ~/Applications/ 文件夹下,dmg 文件挂载后还要再进行一下弹出的动作,经历的次数多了就会思考如何简化这个操作。
用 LaunchBar 来实现自动化操作
最初我是使用 Automator 的 Folder Action 功能来完成这个需求的,但是并不是所有的 dmg、zip 内都是可以直接复制到~/Applications/ 的 .app 文件(比如从 Parallels Desktop 网站上下载下来的 DMG 内就是需要双击后才运行的 Install Parallels Desktop.app),所以 Automator Folder Action 的局限也很明显:它无法做区分,会闷头闷脑的对所有符合筛选条件的文件都做操作,易产生错误。这时候就有了更高更细致的需求。
LaunchBar 就很适合快速筛选,控制传入参数并给出及时的动态反馈。
最后完成的效果:激活 LaunchBar → 运行「Install Apps」动作,这时动作会列出~/Downloads下的 dmg、zip、app、pkg 文件,用方向键选择需要处理的文件后回车。
如果选择的是 dmg 文件:会自动进行挂载 → 然后自动提取 dmg 中的 app 文件 → 如果该 app 正在运行,会自动退出 → 自动复制到 ~/Applications/下 → 弹出 dmg 文件并删除 → 重新打开该 app
如果选择是 zip 文件:zip 文件会解压后,提取其中的 app 文件,并复制到~/Applications/下,同样的也会询问是否删除:
如果选择的是 pkg 文件:则会直接运行:
如果是 dmg 文件中又包含了 zip 文件的情况,会递归查找,直至找到 .app 文件。
如果你对该动作的制作流程感兴趣,还可以接着往下看。
如何制作
打开 LaunchBar,输入AE 打开 LaunchBar Action Editor,点左下角加号新建一个 New Action,命名为「Install Apps」。
本质上所有的 LaunchBar Action 其实都是 .lbaction 文件,是 Package 类型,其结构与大部分 macOS 的 bundle 文件都是类似的,填在 General 部分关于 Action 的元信息会被保存在 Info.plist 下。当 Action 还在开发的过程中时,建议勾选「Enable Debug Log」,通过 /Applications/Utilities/Console.app 查看运行情况。
关于 Info.plist 中所有的 metadata 信息的文档可以查阅这里。 随后来到 Scripts 部分。
关于「Scripts」
在一个 LaunchBar Action 内可以提供不同的几种 Scripts 用于不同的用途,具体而言:
Default Script:每个动作都需要有的脚本,是在大部分情况下运行的脚本,同时也是在按下 ↩ 时、使用「Send To」动作激活动作时和把光标移到这个动作上然后按→时会运行的脚本。
Suggestions Script:提示脚本或者叫建议脚本,在文本输入期间会运行的脚本,根据已经输入的字符,猜测用户可能的输入意向。当用户输入了完整的搜索词,或选中了一个建议,按下 ↩,对应的字符串会作为输入传递到 Default Script。
Action URL Script:LaunchBar 支持 URL Scheme,当在其他程序中通过自定义的 URL Scheme 激活 LaunchBar Action 时,会运行 Action URL Script。
几个属性:
Run in background:动作是否需要在后台运行。如果不选中此项,LaunchBar 会保持在前台运行,直至运行结束或返回了用户结果。
Live feedback enabled:动态反馈。选中此项,脚本会动态的对用户输入做出反馈。与 Suggestions Script 类似,但对结果的处理方式不同。
Keep window active:保持窗口活跃。LaunchBar 窗口在脚本运行过程中会保持运行在前台如果选中此项。
Reapply last string argument with ⇧ ↩:选中后,在 Action 中按下 ⇧ ↩ 会再次输入上次的输入内容。
Requires argument:如不勾选,Action 会直接执行。勾选后,Action 可以接受参数。
Accepts string argument 与 Accepts path argument:规定了 Action 能接受哪种类型的输入。
Return result:LaunchBar Action 能够解析 Action 输出的 JSON、XML、PLIST 格式的复杂结果,但是如果 Action 只需要返回简单的结果,在此处做选择,可以强制 LaunchBar 把结果作为字符串或路径来处理。
对于「Install Apps」这个动作,我使用了 Default Script 和 Suggestions Script,皆使用 Shell Script 完成,可能需要您有一些基础的 Shell 编程基础。 具体实现
功能上,Suggestions Script 负责从 ~/Downloads/中筛选出需要的 zip、app、dmg、pkg 类型文件。Default Script 把从 Suggestions Script 传递来的文件做进一步处理:解压、复制、挂载或打开,并询问用户相关细节。
Suggestions Script
前半部分基础工作不谈,只提一下与 LaunchBar 交互的部分:
echo '<?xml version="1.0"?><items>'
while IFS= read -r line
do
FILE=$(basename "$line")
KBSIZE=$(du -ks "$line" | awk '{print $1}')
SIZE=$(echo "scale=2;x=($KBSIZE/1024); if (x<1) print 0; x" | bc)
ADDED=$(stat "$line" | awk '{print $22, $21, $24}' | sed 's|\"||g')
SETICON=$(icon "$line")
echo '<item>
<arg>'$line'</arg>
<title>'$HOME'/Downloads/'$FILE'</title>
<subtitle>Size: '$SIZE' MB | Date Added: '$ADDED' | '$line'</subtitle>
<icon type="fileicon">'$SETICON'</icon>
</item>'
done <<< "$TABELA"
echo '</items>'
为了使 LaunchBar 能够解析脚本反馈回的(复杂)数据,需将数据按照相关标签的规定以 XML 形式返回。 Default Script
Default Script 中,使用isrunning()函数来判断 app 是否正在运行,如果正在运行则询问用户是否退出。使用checkzip() 和checkdmg() 分别对 zip 和 dmg 文件做处理。
因为 Shell Script 无法使用图形界面与用户进行互动,相关的位置使用了 Apple Script 来实现系统通知,弹出对话框等操作,像:
delete(){
osascript <<EOF
set the_path to "$QUERY"
set the_folder to (POSIX file the_path) as alias
set answer to the button returned of (display dialog "The process of copying / installing app has ended.\n\nWould you like to remove source file now?" buttons {"Yes", "No"} default button 2 with icon caution with title "Install App")
if (answer = "Yes") then
tell application "Finder"
move the_folder to trash
end tell
end if
EOF
}