脚本开发指南

本文档面向已经会写一些 shell / python / applescript 的用户——讲清楚菜单脚本能用哪些解释器、怎么传参、环境怎样、怎么调试。如果你刚开始用,先看 脚本菜单(基础)


1. 解释器选择

iRightMenu Pro 内置以下 alias,”脚本类型”字段写哪个名字就调用哪个解释器:

你写的脚本类型 实际调用
bash / sh / shell /bin/bash
zsh /bin/zsh
python / python3 /usr/bin/python3
applescript /usr/bin/osascript
javascript /usr/bin/osascript -l JavaScript(JXA)
ruby /usr/bin/ruby
perl /usr/bin/perl
swift /usr/bin/swift
php /usr/bin/php

任何其他名字(如 luanodegorust…)都通过 /usr/bin/env <name> 在 PATH 里查找——只要解释器装在 PATH 里就能用。

也可以直接填完整路径作为脚本类型,例如:

  • /opt/homebrew/bin/python3(Homebrew 的 Python)
  • /Users/你/.pyenv/shims/python(pyenv)
  • /opt/homebrew/bin/node

填完整路径最稳定——不依赖 PATH 优先级。


2. Shell 脚本

最常用。脚本类型填 bash / sh / zshiRightMenu 自动添加 shebang,你不需要自己写 #!/bin/bash

参数:

  • $1$2$3… = 选中文件的绝对路径
  • "$@" = 所有选中项(推荐写法)
  • $# = 选中项数量
echo "选中了 $# 个项目"
for f in "$@"; do
    if [ -d "$f" ]; then
        echo "[文件夹] $f"
    else
        echo "[文件]   $f"
    fi
done

踩坑提醒

  • 路径含空格——必须双引号包变量,永远写 "$f" 不要 $f
  • set -e 让脚本第一个失败立即退出(除非你想容错)
  • 脚本是非交互式 shell,不读 ~/.zshrc 里的 alias / function

3. Python 脚本

脚本类型填 pythonpython3,都映射到 /usr/bin/python3(macOS 自带的 Python 3)。

参数:sys.argv[1:] 是选中文件路径列表(argv[0] 是临时脚本路径本身,跳过)。

import sys

for path in sys.argv[1:]:
    print(f"处理: {path}")

用 Homebrew / pyenv / conda 的 Python 怎么办?

默认 /usr/bin/python3 没有第三方库(如 requestsnumpy)。两种办法:

  • 填完整路径作为脚本类型/opt/homebrew/bin/python3/Users/你/.pyenv/shims/python(推荐)
  • /usr/bin/env python3 作为脚本类型:让 PATH 里第一个匹配的 python3 生效——iRightMenu 默认 PATH 已包含 Homebrew 路径

4. AppleScript / JavaScript(osascript)

脚本类型分别填 applescriptjavascript。底层走 /usr/bin/osascript(JS 加 -l JavaScript)。会自动加 shebang。

重要差异:osascript 不像 bash 那样自动暴露 $1 $2。要在脚本里拿到选中文件路径,必须用专门的入口:

AppleScript:on run argv

on run argv
    repeat with filePath in argv
        log "处理: " & filePath
    end repeat
end run

控制其他 App 的标准模板:

on run argv
    set firstFile to item 1 of argv
    tell application "BBEdit"
        activate
        open POSIX file firstFile
    end tell
end run

JavaScript(JXA):function run(argv)

function run(argv) {
    for (let path of argv) {
        console.log("处理: " + path);
    }
}

注意 console.log 输出到 stderr,要在”结果对话框”里显示就用 return value


5. 选中文件 / 参数

基本规则

  • 参数 = 选中文件的绝对路径
  • 文件文件夹都按相同方式传入,脚本不区分类型
  • 工作目录 = Finder 当前所在目录(即菜单点击时所在的窗口目录)

路径含空格

macOS 用户目录有大量含空格路径(Application Support / Group Containers / 中文目录名等)。所有变量都要双引号包

# ❌ 空格会被 shell 拆成多个参数
cp $1 /tmp/

# ✅ 正确
cp "$1" /tmp/

状态栏菜单:没有参数

放在状态栏的脚本菜单无选中文件$@ 是空的、$# 是 0。可以用这个判断”是从状态栏触发”:

if [ $# -eq 0 ]; then
    # 状态栏触发——通常做"全局"动作
    open -a "Calculator"
fi

6. 环境变量与 PATH

默认 PATH

脚本拿到的 PATH 已经追加了 Homebrew 等常见路径:

/usr/local/bin
/opt/homebrew/bin
/opt/homebrew/sbin
/usr/local/opt/ruby/bin
/opt/local/bin
$HOME/.local/bin

意味着 Homebrew 装的命令(brewcodeghfzf 等)直接可用

继承 ~/.zshrc 的自定义

脚本运行的是非交互式 shell,不会读 ~/.zshrc / ~/.bash_profile。所以:

# ~/.zshrc 里写了:
# alias ll='ls -lh'
# export MY_TOOL_HOME="/Users/h4ck/tools"

# 脚本里:
ll              # ❌ command not found
echo $MY_TOOL_HOME    # ❌ 输出空字符串

解决方法(按推荐度):

  1. 直接用完整命令(脚本里写 ls -lh 替代 ll
  2. 在 Inspector 填”自定义 PATH”,让额外目录里的二进制能找到
  3. 偏好设置 → 高级 → Shell 环境,写全局 KEY=VALUE 行(每行一个),所有脚本继承

调试:看脚本能拿到什么

不知道环境长啥样?跑这段,”反馈方式”设”结果对话框”看输出:

echo "=== PATH ==="
echo "$PATH" | tr ':' '\n'
echo "=== 全部环境变量 ==="
printenv | sort
echo "=== 工作目录 ==="
pwd
echo "=== 参数 ==="
echo "$#: $@"

7. 输出与反馈

“反馈方式”决定脚本运行成功时用户看到什么:

反馈方式 行为
静默执行——成功无反馈
通知 系统通知”执行完成”——不显示 stdout 内容
结果对话框 弹对话框,显示 stdout + stderr 合并的输出(输出过长会截断)

失败时的强制反馈

脚本退出码非零(exit 1 / 命令失败 / 语法错),无论反馈方式怎么设,都会弹错误对话框(含”查看日志”按钮)。这是兜底机制——避免静默失败。

怎么选反馈方式

  • :批量操作没必要骚扰用户(如批量改权限)
  • 通知:操作成功有意义但不需要细节(如复制 N 个路径成功)
  • 结果对话框:用户需要看脚本的具体输出(如 du -sh 显示大小、md5 看哈希值)

在脚本里访问剪贴板 / 控制其他 App

脚本可以正常用 pbcopy / pbpasteosascriptopen -a 等需要”用户登录会话”的命令,剪贴板写入会立即生效,能被剪贴板管理器(Paste / Maccy 等)正确捕获。

# 复制路径到剪贴板(shell-quoted 格式,可直接粘到 Terminal 用)
printf '%q ' "$@" | pbcopy
osascript -e "display notification \"已复制 $# 个路径\" with title \"iRightMenu\""

8. 文件匹配规则

匹配规则决定”菜单在哪些选中项上显示”。设置错了会出现”右键看不到菜单”的常见 bug。

文件匹配类型

类型 规则
不限扩展名,所有文件都显示
扩展名 列表里的扩展名才显示(如 ["py", "js"]),大小写不敏感
正则 用正则匹配文件名(不含路径),大小写敏感

文件夹匹配类型

只支持 / 正则(没有”扩展名”——不过 .app 是扩展名 + 文件夹的组合,正则用 \.app$ 即可)。

仅可执行文件

勾上”仅可执行文件”,菜单只在文件具有 +x 权限时显示。注意是权限位检查,不分辨二进制类型——chmod +x somefile.txt 也算可执行。

数量限制

  • 最少文件数 / 最多文件数 都默认 0 = 无限制
  • 11 = “只在选中单个文件时显示”
  • 先按类型过滤,再算数量

文件 vs 文件夹的组合

显示在文件 显示在文件夹 行为
只在选中”全是文件”时显示
只在选中”全是文件夹”时显示
文件 / 文件夹混选都显示
通用菜单:匹配所有选中项;在 Finder 空白处右键时显示

最后一行有特殊语义——既不限文件也不限文件夹的菜单同时会出现在”空白处右键”的菜单里。


9. 管理员权限脚本

把”执行权限”设为 root,脚本会以管理员身份运行。首次使用时系统弹一次授权对话框,后续不再弹。

限制(重要)

  • 不能弹对话框——管理员权限脚本无 GUI 上下文
  • 不能控制其他 App(osascript / open -a 都不行)
  • 不能写剪贴板(pbcopy 在管理员会话里写到的不是你看到的剪贴板)
  • 不读用户登录态——你的 ssh-agent / keychain unlock 状态不可用
  • 没有超时机制——脚本卡住会一直卡

适合的场景

  • 修改系统配置文件(/etc/hosts/Library/... 等)
  • 操作 SIP 之外的系统目录
  • 跑需要管理员的诊断命令(tcpdumpdtrace

适合的场景

  • 任何 GUI 操作(弹窗 / 控制 App / 写剪贴板)
  • 用户级配置(git config --global / defaults 读用户偏好)
  • 需要”看用户当前界面”的操作

如果脚本既要管理员又要 GUI,拆成两个菜单——管理员菜单做系统操作,普通菜单做 UI 反馈。


10. 调试与日志

每个脚本独立的日志文件

每次脚本执行后,元信息和输出会写到日志文件——iRightMenu Pro → 偏好设置 → 高级 → “打开日志目录”,下面的 Scripts/<菜单标题>.log 就是完整记录。

每段记录含:

  • 时间戳
  • 脚本类型
  • 退出码
  • 工作目录
  • 选中文件列表
  • stdout + stderr 输出

调试三步法

  1. 先在 Terminal 跑通——把要做的事在 Terminal 里手动执行一次,确认逻辑对
  2. 粘贴到菜单——把跑通的代码粘贴成菜单脚本,反馈方式设”结果对话框”,再跑一次
  3. 看日志——如果还是不对,看 Scripts/<菜单标题>.log 里的完整输出 + 环境变量 + 退出码

常见症状 → 排查方向

症状 常见原因
右键看不到菜单 文件匹配规则不对(扩展名 / 数量 / 文件 vs 文件夹)
command not found 默认 PATH 不含该命令所在目录——填”自定义 PATH”或用绝对路径
路径解析错误 变量没用双引号包,含空格的路径被拆
输出空 / 没反应 “反馈方式”设为”无”——临时改”结果对话框”看输出
AppleScript / JS 拿不到参数 没用 on run argv / function run(argv) 入口(见 §4)

11. 进阶菜谱

下面这些是详细版的进阶例子。简单的菜谱(VSCode 打开 / 总大小 / md5 等)见 基础页

复制 file:// URL(含 URL 编码)

把路径转成 file:// URL,方便贴到浏览器或 Markdown:

import sys
import urllib.parse
import subprocess

urls = ["file://" + urllib.parse.quote(p) for p in sys.argv[1:]]
text = "\n".join(urls)
subprocess.run(["pbcopy"], input=text, text=True)
print(f"已复制 {len(urls)} 个 file:// URL")

AppleScript:用 BBEdit 打开多个文件

on run argv
    tell application "BBEdit"
        activate
        repeat with p in argv
            open POSIX file p
        end repeat
    end tell
end run

Python:批量加日期前缀

import sys
import os
import datetime

prefix = datetime.date.today().isoformat() + "_"
for path in sys.argv[1:]:
    dir_, name = os.path.split(path)
    if name.startswith(prefix):
        continue   # 已加过前缀,跳过
    os.rename(path, os.path.join(dir_, prefix + name))
print(f"已重命名 {len(sys.argv) - 1} 个文件")

状态栏快速:在 Finder 当前位置开 Terminal

放状态栏菜单(无文件参数):

tell application "Terminal"
    activate
    do script "cd " & quoted form of (system attribute "PWD")
end tell

12. 何时该写 Lua 插件

脚本到达以下”临界点”时,该考虑改成 Lua 插件:

  • 需要 UI 表单:让用户选证书 / 选选项 / 输入配置——脚本撑不了,Lua 插件有完整表单 API
  • 需要持久化状态:跨菜单调用记住设置——脚本只能写文件,插件有 keychain / sqlite API
  • 跨菜单复用:同一段逻辑被多个菜单触发——脚本要复制粘贴,插件可一份代码导出多个菜单项
  • 想分发给别人:脚本只能复制粘贴文本,插件可以上传到插件商店,他人一键安装

进一步阅读:插件开发指南API 参考示例插件

开发者文档
使用帮助
使用说明 脚本菜单 常见问题
脚本开发
开发指南
插件开发
快速开始 开发指南 示例插件
API 参考
概览 API 查询 插件信息 日志 Finder 上下文 插件设置 国际化
UI 与交互
对话框 进度条 系统通知 选择器 WebView 状态栏 Dock
文件与路径
文件操作 路径工具 Finder 操作 废纸篓 扩展属性 元数据 文件监听
数据格式
JSON Plist CSV XML PDF 图片
文本与编码
字符串 正则表达式 日期时间 颜色 加密编码
系统
Shell 命令 进程管理 应用管理 系统信息 AppleScript 快捷指令
系统信息
网络信息 电源/电池 屏幕/外观 音频控制 蓝牙设备 位置服务
网络
HTTP 请求 WebSocket URL 工具
输入与剪贴板
键盘模拟 鼠标模拟 全局热键 剪贴板 窗口管理
存储
SQLite Keychain UserDefaults
媒体
文字识别 二维码
工具
归档 类型标识 分享 定时器 防休眠 并发/协程