第一次认真对待 VSCode 里的 Task,是因为一件特别小的事:我在做一个 C 语言的小工具,每改一次代码就要切到终端,敲一遍gcc main.c -o main -Wall -g,再敲一遍./main,反复十几次之后,手指比脑子还熟练。后来同事路过看了一眼,说"你为什么不按 Ctrl+Shift+B?"那一刻我才意识到,VSCode 里藏着一套几乎没人系统讲过、但一旦用上就回不去的东西——Task 系统。
VSCode Task 本质上是把"你在终端里手敲的一串命令"固化成一份可复用、可组合、可被其他功能调用的配置文件。它不是什么高深的构建系统,也不替代 Make、CMake 或者 npm scripts;它更像一个"命令调度层",负责把编译、打包、跑服务、跑测试这些动作,用统一的入口收拢起来。你可以在编辑器里一键触发,也可以让调试器在启动前自动跑一遍,还能让多个任务按顺序或者并行地铺开。适合谁看?适合所有每天要在终端和编辑器之间来回切换的人:写 C/C++ 的、写 Python 的、做前端的、维护脚本的,以及那些"想自动化但不知道从哪下手"的开发者。
下面我按自己在几个项目里踩过的路,把 Task 从配置文件结构到实际落地完整拆一遍。不堆概念,重点讲每个字段为什么这么写、写错了会出什么症状。
1. VSCode Task 在工程里扮演的角色
1.1 它和终端、脚本、构建工具的分工
很多人第一次接触 Task 会产生一个误解:以为它是"构建系统"。其实不是。构建系统(Make、CMake、Gradle、npm scripts)负责描述依赖关系和构建规则,而 Task 负责"调用谁来执行"。举个直观的例子:你项目里已经有一个Makefile,那 Task 里大概率只写一行"command": "make";你的前端项目里已经配好了npm run build,Task 里就是"command": "npm", "args": ["run", "build"]。
这个定位决定了 Task 的两条设计原则。第一,它不重复造轮子,只做调度,所以配置通常很短。第二,它的价值体现在"入口统一"上——不管底层是 make 还是 npm 还是 python,在 VSCode 里都是Ctrl+Shift+B或者命令面板里同一个位置。
我自己判断"这个动作值不值得写成 Task",标准很简单:这个命令我一周会敲超过五次吗?会,就写成 Task。比如编译、跑单测、启动 dev server、格式化代码、拉取依赖。不会的,比如一次性迁移脚本,就没必要,配置维护成本比收益高。
1.2 .vscode/tasks.json 的位置与作用域
Task 配置文件固定叫tasks.json,放在项目根目录的.vscode文件夹下。这一点必须记牢,因为放错位置是新手最常犯的错。VSCode 只认两个位置:
- 工作区级:
<项目根>/.vscode/tasks.json,只对当前打开的文件夹生效,可以随代码一起提交到仓库,团队共享。 - 用户级:通过命令面板执行
Tasks: Open User Tasks生成,对所有项目生效,适合放"全局通用"的任务,比如"用当前文件跑一次 Python"。
区别在于:工作区级配置能访问${workspaceFolder}这类变量,并且会被 Git 跟踪;用户级配置更私有,但拿不到具体项目的上下文。我的习惯是,凡是跟项目目录结构相关的任务,一律写在工作区级;凡是"和哪个项目无关、只跟当前打开的文件有关"的任务,放用户级。
注意:
tasks.json允许写注释(它是 JSONC 而不是严格 JSON)。这一点很实用,我通常会在每个任务上面用注释标一句"这个任务干什么、什么时候用",半年后回来看还能秒懂。
1.3 自动检测到底检测了什么
VSCode 有个很贴心的机制叫 Task 自动检测(Auto-detect)。打开一个项目后执行Tasks: Run Task,你经常会发现列表里已经躺着一堆任务,比如npm: build、npm: test、tsc: build、make: all。这不是魔法,是 VSCode 内置了一批"任务提供者"(Task Provider),它们会去嗅探项目里的特征文件:
| 特征文件 | 自动生成的任务 |
|---|---|
package.json的 scripts 字段 | 每个 script 一条任务,标签形如npm: xxx |
tsconfig.json | TypeScript 编译任务 |
Makefile | 按目标生成 make 任务 |
gulpfile.js/Gruntfile.js | 对应构建任务 |
.csproj/build.gradle等 | 对应语言的构建任务 |
关键在于:自动检测出来的任务不需要你写 tasks.json。很多人一上来就手写配置,其实先去Tasks: Run Task看一眼,十有八九已经有现成的了。只有当自动检测覆盖不了、或者你想改参数(比如加编译选项、改工作目录)时,才需要手动创建。
这里有个实用技巧:在命令面板执行Tasks: Configure Task,如果选中一个自动检测到的任务,VSCode 会自动生成一段对应的 JSON 骨架,你在这基础上改就行,不用从空白开始写。我几乎所有的 tasks.json 都是这么起手的。
2. tasks.json 的字段:每个键解决什么问题
2.1 label、type、command、args 构成的最小骨架
一个能跑的任务,最少需要四个字段:
{ "version": "2.0.0", "tasks": [ { "label": "build-debug", "type": "shell", "command": "gcc", "args": ["-Wall", "-g", "main.c", "-o", "main"] } ] }label是任务的名字,也是它在你所有调用入口里的唯一标识——快捷键、dependsOn、launch.json的preLaunchTask引用的都是它,所以改名要全局搜一遍,这是最容易漏的地方。version固定写"2.0.0",不写会有一堆兼容提示。
type只有两个值,差别很大:
shell:命令交给系统 shell 执行。意味着支持管道、重定向、&&、通配符展开。绝大多数场景用这个。process:直接 spawn 一个进程,不经过 shell。参数不会被 shell 解析,所以路径里有空格也不用额外转义,但也因此不支持&&这类语法。
我一般默认shell,只有在"参数里带特殊字符、被 shell 吃掉"的时候才切process。
args建议写成数组而不是拼成一整个字符串。写成数组时,VSCode 会帮你处理跨平台的引号问题(虽然不完美,后面踩坑那节会说);拼成一整个字符串,Windows 和 Linux 上的行为差异会让你怀疑人生。
2.2 options 与 presentation:决定任务跑在哪、长什么样
这两个字段不影响任务能不能跑,但极大影响"跑起来舒不舒服"。
options控制执行环境:
cwd:工作目录,默认是${workspaceFolder}。如果你的 Makefile 在子目录里,这里必须改,否则就是"找不到 Makefile"。env:注入环境变量。比如给 Python 任务注入PYTHONPATH,或者临时改PATH。shell:指定用哪个 shell 执行。默认跟随系统,想在 Windows 上用 bash 就得在这里配置,并且要注意 VSCode 有个独立的终端配置项,两者的优先级不一样。
presentation控制终端面板的表现,常用键:
| 字段 | 作用 | 建议值 |
|---|---|---|
reveal | 任务启动时是否显示面板 | always(需要看输出)或silent(后台跑) |
focus | 是否把焦点切到终端 | 一般false,否则会打断你打字 |
panel | 复用哪个终端面板 | shared复用,dedicated独占 |
clear | 运行前是否清屏 | 编译任务建议true,日志任务建议false |
echo | 是否回显执行的命令 | true,方便排查 |
showReuseMessage | 复用终端时是否提示 | false,清净一点 |
这里有个我踩过的坑:panel: "shared"看着优雅,但如果你并行跑三个任务,它们的输出会全部灌进同一个终端面板,日志交错在一起,根本没法看。我的做法是:并行任务一律dedicated,单任务链用shared。
2.3 group 与 isDefault:让 Ctrl+Shift+B 认识你
group决定任务归属于哪个"语义分组",有两个值:build和test。它的实际作用是接管快捷键:
Ctrl+Shift+B触发默认构建任务,也就是group为build且isDefault: true的那个。- 命令面板里的
Tasks: Run Test Task触发默认测试任务。
{ "label": "build-debug", "type": "shell", "command": "make", "group": { "kind": "build", "isDefault": true } }如果把多个任务都标成isDefault: true,VSCode 会让你选一个,体验反而变差。所以同一时间只保留一个默认构建任务,是我一直坚持的习惯。想切换"当前用哪个构建任务",改这一行就行,比记一堆快捷键省事。
2.4 dependsOn 与 dependsOrder:串联还是并行
dependsOn让任务可以组合。如果一个任务只有dependsOn而没有command,它就是一个"复合任务"(compound task),本身不干活,只负责调度别人:
{ "label": "全量构建", "dependsOn": ["清理产物", "编译", "跑测试"], "dependsOrder": "sequence" }dependsOrder的差别非常关键:
sequence:严格按数组顺序,前一个跑完(退出码为 0)才跑下一个。用于有依赖关系的场景,比如必须先清理再编译。parallel(默认):同时启动所有依赖任务。用于互不干扰的场景,比如同时拉起前端和后端服务。
有个细节文档里写得含糊但实测确认:sequence 模式下任何一个任务返回非零退出码,后续任务就不会执行。这既是保护也是坑——如果你的测试任务是"跑完就返回非零"(有些测试框架默认这样),后面的任务永远跑不到。要么通过args关掉这种退出码行为,要么把它拆到末尾。
另一个容易被忽略的是dependsOn也支持对象写法{"label": "xxx", "dependsOn": ...},用来调整某个依赖任务的单独行为,但在实际项目里我很少用,数组写法已经够清晰了。
3. 变量与输入:别把路径写死
3.1 预定义变量清单和"空值"陷阱
Task 最实用的能力之一,是内置了一堆变量,让你写出"跟当前文件走"的通用任务。常用的一批:
| 变量 | 含义 |
|---|---|
${workspaceFolder} | 当前项目根目录的绝对路径 |
${file} | 当前活动编辑器的文件绝对路径 |
${relativeFile} | 当前文件相对项目根的路径 |
${fileDirname} | 当前文件所在目录 |
${fileBasename} | 当前文件名(带扩展名) |
${fileBasenameNoExtension} | 当前文件名(不带扩展名) |
${fileExtname} | 当前文件扩展名 |
${lineNumber} | 光标所在行号 |
${selectedText} | 当前选中的文本 |
${env:NAME} | 读取环境变量 |
${config:editor.fontSize} | 读取 VSCode 配置项 |
${input:变量名} | 引用inputs里定义的交互输入 |
${pathSeparator} | 当前系统的路径分隔符 |
这里的大坑是:凡是带file的变量,只有在"从编辑器上下文触发"时才有值。如果你是右键任务列表点执行,而当时没有打开任何文件,或者从命令面板触发,${file}就会展开成空字符串,于是命令变成gcc -o main这种,报一堆莫名其妙的错。
我遇到过最典型的症状是:任务在编辑器里按快捷键跑得好好的,一旦绑到"保存时执行"或者从任务列表里点,就报"找不到输入文件"。排查了半小时才发现是${file}为空。解决办法是任务里显式用${workspaceFolder}或者写死相对路径,不要强依赖"当前文件"。
3.2 ${input:} 三种交互方式
inputs让任务在运行前弹窗问你参数,适合"同一个任务要跑不同参数"的场景:
{ "label": "运行目标", "type": "shell", "command": "./${input:targetName}", "problemMatcher": [], "inputs": [ { "id": "targetName", "type": "promptString", "description": "要运行哪个可执行文件?", "default": "main" } ] }三种type:
promptString:弹输入框,可以设default和password: true(输入内容打码)。适合输入端口号、版本号、分支名。pickString:下拉选择,通过options数组给出候选项,还能给每一项加label和value,用description补充说明。适合"环境二选一"的场景。command:调用某个命令返回结果作为值。这个最灵活也最难查错,因为返回值不对时,你只能看到命令莫名其妙失败。
我个人的经验:promptString用得多,pickString用在容易记错参数的场景(比如环境名),command只在确实需要动态取值时才用,否则排查成本太高。
3.3 跨平台分支:别指望一条命令通吃
tasks.json支持windows/linux/osx三个平台分支,用来覆盖某个任务的字段:
{ "label": "清理", "type": "shell", "command": "rm -rf build", "windows": { "command": "powershell", "args": ["-Command", "Remove-Item -Recurse -Force build"] } }这个写法看着啰嗦,但在跨平台项目里几乎是必需品。我维护过一个工具库,团队里有人用 Windows 有人用 Linux,没有平台分支之前,rm -rf在 Windows 上直接报"不是内部或外部命令",每次新人配环境都要问一遍。加了分支之后,文档里那句"请先手动删除 build 目录"终于可以删掉了。
提示:如果你的命令涉及删除、覆盖等破坏性操作,建议在
args里保留显式的目标路径,不要用变量拼出通配符再去删。任务一旦被误绑到快捷键上,后果不好收拾。
4. problemMatcher 与后台任务:从能跑变好用
4.1 problemMatcher 怎么把编译输出变成问题列表
如果说 Task 有一半价值在"一键触发",那另外一半就在problemMatcher。它的作用是把任务的终端输出里的错误行,解析成 VSCode 的"问题"面板条目,顺便在代码行旁边标红波浪线,点一下直接跳过去。
VSCode 内置了一批现成的匹配器,直接引字符串就行:
$gcc:匹配 gcc/clang 的报错格式。$tsc:匹配 TypeScript 编译器输出。$eslint-stylish:匹配 ESLint 的默认输出。$msCompile:匹配 MSVC 的错误格式。
用法就是在任务里加一行:
{ "label": "build-debug", "type": "shell", "command": "gcc", "args": ["-Wall", "-g", "main.c", "-o", "main"], "problemMatcher": ["$gcc"] }有个反直觉的点:对不支持匹配器的任务(比如只是跑个脚本),建议显式写"problemMatcher": []。不写的话,VSCode 可能会沿用某个默认行为,在某些版本上会傻等一会儿。加上空数组就是明确告诉它"这个任务不产出问题"。
用上之后我的体验变化很明显:以前编译报错要去终端里翻、记行号、回到编辑器定位,现在问题面板直接列出来,点一下就跳。对 C/C++ 这种报错密集的场景,效率差别不是一点半点。
4.2 自定义匹配器:多行正则的写法
当输出格式不在内置列表里(比如你自己写的一套 lint 脚本),就需要自定义匹配器:
"problemMatcher": [ { "owner": "my-lint", "fileLocation": ["relative", "${workspaceFolder}"], "pattern": { "regexp": "^(.+?):(\\d+):(\\d+):\\s+(error|warning):\\s+(.+)$", "file": 1, "line": 2, "column": 3, "severity": 4, "message": 5 } } ]几个必须注意的点,都是我实际调试出来的:
第一,正则里的反斜杠要写两次。JSON 里\d必须写成\\d,不然解析直接失败。
第二,fileLocation决定路径怎么解析。如果是相对路径,推荐写["relative", "${workspaceFolder}"],这样点问题条目能正确跳到文件。写错的话,问题面板里文件名会变成灰色不可点。
第三,匹配不上的时候不要急着怀疑正则语法。先用Tasks: Run Task手动跑一遍,看终端里真实输出的每一行——很多时候是程序把错误输出到了 stderr,而某些配置下它没有和 stdout 一起被捕获,这种情况需要检查任务是不是用了process类型,或者命令里有没有把 stderr 重定向掉。
第四,多行匹配(一个错误跨多行,比如错误信息后面跟一行代码片段)需要把pattern写成数组,前一项匹配首行,后一项匹配后续行,并配合loop字段。这套写法我建议直接抄官方示例再改正则,手写很容易少一个字段导致静默失效——它不会报错,只是匹配不到。
4.3 isBackground 与 background:让长驻服务和调试配合
默认情况下,VSCode 认为任务"跑完就退出"。但 dev server、文件监听这类任务永远不会退出,如果按默认处理,VSCode 会一直等它结束,dependsOn后面的任务永远启动不了,调试器的preLaunchTask也会卡住。
解决办法是把它标成后台任务,并且告诉 VSCode "什么时候算启动完成":
{ "label": "start-dev-server", "type": "shell", "command": "npm", "args": ["run", "dev"], "isBackground": true, "problemMatcher": [ { "owner": "dev-server", "pattern": { "regexp": "^$" }, "background": { "activeOnStart": true, "beginsPattern": "Starting development server", "endsPattern": "Compiled successfully" } } ] }beginsPattern和endsPattern是关键:只有当终端输出匹配到endsPattern时,VSCode 才认为这个后台任务"准备好了",才会继续跑依赖它的任务或者启动调试。如果这两个正则写错,你会看到界面一直转圈,或者直接报"任务未在预期时间内完成"。
我调这个字段的时候有个笨但有效的办法:先不加background,正常跑一遍,把服务启动前后那几行输出原样复制出来,再从中挑两句特征明显、不会被误匹配的话当起止标志。这里要特别注意别选太通用的词,比如只写"ready",如果日志里其他行也含有这个词,任务会被判定为"已经就绪"而提前放行。
5. 三类可以直接抄的场景配置
5.1 C/C++:编译、运行、再接到调试器
这是我用得最多的一组。三个任务,一个负责编译,一个负责运行,再加一个复合任务把它们串起来:
{ "version": "2.0.0", "tasks": [ { "label": "c: build", "type": "shell", "command": "gcc", "args": ["-Wall", "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}"], "group": { "kind": "build", "isDefault": true }, "problemMatcher": ["$gcc"], "presentation": { "clear": true, "reveal": "silent" } }, { "label": "c: run", "type": "shell", "command": "${fileDirname}/${fileBasenameNoExtension}", "dependsOn": "c: build", "problemMatcher": [], "presentation": { "reveal": "always", "panel": "dedicated" } } ] }这里presentation.clear: true是有意的:编译日志每次重来,屏幕干净。而运行任务用dedicated面板,是因为程序可能需要等待输入(比如scanf),如果和编译任务共用一个面板,焦点和输入流容易乱。
Windows 上路径分隔符不一样,${fileDirname}/${fileBasenameNoExtension}里的斜杠在 Windows 上也能用,因为 gcc 接受正斜杠;但如果你用的是 MSVC 或者某些工具,就需要在windows分支里改成反斜杠。
配合调试也很简单,在launch.json里加:
{ "name": "调试当前文件", "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}.exe", "preLaunchTask": "c: build" }preLaunchTask的值必须和label完全一致,包括大小写和空格。我因为这个白白浪费过一次时间:任务明明能跑,调试就是报"找不到 preLaunchTask",对比了半天发现 label 是c: build,而写的是c:build,少个空格。
5.2 前后端多服务并行启动
做全栈或者微服务本地联调的时候,最烦的是开三四个终端窗口挨个敲命令。用复合任务可以一次拉起:
{ "label": "启动全部服务", "dependsOn": ["启动网关", "启动用户服务", "启动前端"], "dependsOrder": "parallel", "problemMatcher": [] }三个子任务各自标isBackground: true,并配好background的起止正则,presentation.panel全用dedicated,这样每个服务的日志互不干扰,还能单独查看。
这套配置有个副作用要提前知道:所有服务都挂在同一个 VSCode 窗口下,关窗口的时候它们会被一起终止。这既是优点(清理干净)也是缺点(误关一下全断)。如果你更喜欢"关掉窗口服务继续跑",那就不该用 Task,直接在独立终端里跑更合适。
还有一个细节:parallel模式下如果某个服务启动失败,VSCode 不会自动杀掉其他已经起来的服务。所以我在复合任务里通常会把最容易失败的(比如依赖数据库的服务)单独拎出来,先手动确认它能起来,再放到组合里。
5.3 Python:虚拟环境怎么被任务正确识别
Python 场景最常见的问题不是命令写错,而是"任务用的是系统解释器,不是你项目里的虚拟环境"。原因很简单:VSCode 的 Task 在 shell 里执行,shell 的环境变量不一定包含虚拟环境的激活状态。
有两种处理方式。一种是在任务里写死虚拟环境里的解释器路径:
{ "label": "py: run", "type": "shell", "command": "${workspaceFolder}/.venv/bin/python", "args": ["${file}"], "windows": { "command": "${workspaceFolder}/.venv/Scripts/python.exe" }, "problemMatcher": [], "presentation": { "reveal": "always" } }另一种是用options.env注入PATH和PYTHONPATH:
"options": { "env": { "PYTHONPATH": "${workspaceFolder}", "PYTHONUNBUFFERED": "1" } }PYTHONUNBUFFERED这个变量值得单独说一句:不设置它的时候,Python 的输出会先缓存在内存里,等到进程结束才刷到终端。如果你的任务是个长驻服务,就会发现"终端里半天没日志",误以为程序卡死了。加上这个环境变量,日志会实时打出来。这个坑我在第一次写后台服务任务的时候踩过,查了很久才反应过来是缓冲问题。
提示:如果你用的是 Poetry 或 PDM 这类工具,直接在
command里写poetry run python往往比手动拼解释器路径更稳,因为它们会自己处理虚拟环境。
6. 那些文档里不会写的坑
6.1 关闭窗口时"任务仍在运行"的提示
用了一段时间 Task 之后,你肯定会遇到这个场景:某个后台任务(dev server、文件监听、调试代理)还在跑,你点关闭 VSCode 窗口,弹出一个确认框问你是否要终止正在运行的任务,有时候关起来还会拖延一会儿。Windows 上偶尔还会看到和"任务宿主"相关的提示,让人以为是系统层面的问题。
我的排查思路是这样的:先确认到底是哪个任务在跑。打开终端面板,看哪些终端标签旁边还有任务名或者进程指示;不确定的话,用Tasks: Terminate Task挨个终止,或者直接在每个终端里Ctrl+C。找到源头之后再决定处理方式:
- 如果这个任务本来就该是长驻的,检查有没有标
isBackground: true。没标的话,VSCode 会把它当成"还没跑完的任务",关闭时自然要拦你。 - 如果任务跑完其实已经不需要了,但进程没退出(比如脚本里 fork 了子进程),那就要在任务里
close相关终端,或者接受"每次关窗口手动确认"。 - 如果你希望关窗口时干脆利落,不要在
runOptions里开启自动运行类选项,并尽量让任务本身有明确的退出条件。
有一类情况特别隐蔽:任务里调用的脚本本身在后台又拉起了别的进程,Task 认为命令已经返回,但那个子进程还在。这种时候确认框会反复出现,而且任务列表里看不到。处理办法是给命令加上进程组的清理逻辑,或者干脆不用 Task 来管这类服务。
6.2 Windows 上的引号、路径和 &&
跨平台任务里,Windows 的坑集中在这几处:
第一,引号。在args数组里带空格的参数,VSCode 会尝试加引号,但如果参数本身又包含引号(比如传给-D的宏定义),就容易出问题。实测比较稳的做法是参数里避免出现嵌套引号,必要时把整条命令写成一个字符串。
第二,路径分隔符。JSON 字符串里的反斜杠是转义字符,写"C:\build"会被解析成奇怪的字符。要么写"C:\\build",要么统一用正斜杠"C:/build",后者在大多数工具里都被接受,可读性也更好。
第三,&&的可用性。shell类型在 Windows 上默认走cmd或者 PowerShell,两者的链式语法不同。如果你的命令里有&&,在 PowerShell 5 上可能报语法错误(新版本 PowerShell 才支持)。跨平台项目我建议拆成多个任务用dependsOn串起来,而不是在一条命令里堆&&,这样每个平台各写各的,反而更清楚。
6.3 输出被清屏、终端复用导致看错日志
有一次我调试一个编译错误,盯着终端看了十分钟,发现报错内容和代码对不上。后来才反应过来:clear: true加上panel: "shared",让我看到的是上一个任务的残留输出加上新任务的输出混在一起,中间那个清屏动作在滚动日志太多的时候不明显,很容易误读。
判断方法很简单:看终端面板上方的任务名标签。如果标签和你以为跑的任务不一致,那就是复用导致的。解决方式是给容易混淆的任务用dedicated面板,或者干脆在presentation里设showReuseMessage: false但保持每个任务独立面板。
另外一个常见困惑是任务"看起来没反应"。这通常是reveal: "silent"导致的——任务确实在跑,只是面板没弹出来。我一般会在调试阶段先设成always,确认稳定之后再改回silent。
6.4 快捷键绑定和自动运行的取舍
想给任务绑快捷键,在keybindings.json里加:
{ "key": "ctrl+alt+r", "command": "workbench.action.tasks.runTask", "args": "c: run" }args就是任务的label,写错的话快捷键按下去没任何反应,也不报错,这个要特别注意。
还有个更"激进"的用法是runOptions.runOn: "folderOpen",打开项目时自动跑任务。我试过在几个项目里用它自动启动 dev server,体验是好用但危险:如果这个任务本身就慢或者会弹交互输入,每次打开项目都要等它,而且它可能在你还没准备好工作的时候就把端口占上了。我的建议是只给"纯查询类"的任务用这个选项,比如打印一下环境版本;凡是会改文件、占端口、拉网络的任务,都老老实实手动触发。
另外instanceLimit这个字段值得提一下:它限制同一个任务能同时开几个实例。默认值在某些场景下会让你连点两次快捷键开出两个进程,互相抢端口。我一般把它设成 1,按第二下的时候它就不会再开新的,而是复用已有实例。
最后分享一个我用了很久的小习惯:每次写完一个新任务,先不改presentation,用默认配置跑三遍——一遍从命令面板、一遍从快捷键、一遍从文件右键菜单。三个入口都跑通,再把reveal改成silent、把clear打开做最后的体验优化。因为这三个入口传入的上下文不一样,${file}这类变量在其中某一个入口下很可能为空,提前测一遍,比出了事再回头查配置省事得多。