跳转到主要内容
TaskForge 3.0:AI 能读任务了

在不同格式之间转换 Obsidian 任务

Obsidian Tasks 没有把整个库从一种格式改写成另一种的命令,它的文档也是这么说的。下面的转换是能完成这件事的脚本和方案,每一个都用该插件自己文档中的示例行测试过,另外还有一种已有工具的转换:任务行转任务文件,以及反向转换。

基于 Obsidian Tasks 8.6、Task Genius 9.13 和 TaskNotes 4.14 编写,2026 年 10 月。字段名和符号列在速查表里。

动手之前

  1. 备份库. 复制文件夹,或提交到 git。一个要改写 4,000 行的脚本,应当很容易撤销。
  2. 先在副本上操作. 在库的副本上运行转换,用 Obsidian 打开,再运行一个你常用的任务查询。把数量与原库对比。
  3. 先转换,再切换插件. Obsidian Tasks 一次只读取一种格式。先转换这些行,再到设置里更改任务格式,这样插件就不会看到转换到一半的库。
  4. 检查剩下的内容. 在库里搜索旧符号(表情符号用 📅、⏳、⏫;Dataview 用 due::、repeat::),找出方案漏掉的行。

你需要哪种转换

从 到 做法
表情符号行 Dataview 字段 下面的脚本或查找替换方案。
Dataview 字段 表情符号行 下面的反向脚本或方案。
任意一种 Task Genius 无需改写。Task Genius 在同一行里两种都读。在它的设置里选择写入哪一种。
任务行 任务文件 TaskNotes 一次转换一行。TaskForge 命令行可以一次转换很多。
任务文件 任务行 TaskForge 命令行,并会预览任何会丢失的内容。

表情符号转 Dataview 字段

每个表情符号及其值都变成一个带方括号的字段。优先级表情符号变成带单词值的 priority 字段,重复文本则移到括号内。请按这个顺序执行替换:先日期和优先级,最后重复,这样重复文本会在下一个方括号开始的地方结束。

转换前后

之前
- [ ] Send invoice to Acme ⏫ 🔁 every month ➕ 2026-10-01 ⏳ 2026-11-01 📅 2026-11-03 #finance
- [x] Book flights 📅 2026-10-25 ✅ 2026-10-20
- [ ] Order parts 🆔 parts1
- [ ] Assemble 🛫 2026-11-10 ⛔ parts1 📅 2026-11-15
之后
- [ ] Send invoice to Acme  [priority:: high]  [repeat:: every month]  [created:: 2026-10-01]  [scheduled:: 2026-11-01]  [due:: 2026-11-03] #finance
- [x] Book flights  [due:: 2026-10-25]  [completion:: 2026-10-20]
- [ ] Order parts  [id:: parts1]
- [ ] Assemble  [start:: 2026-11-10]  [dependsOn:: parts1]  [due:: 2026-11-15]

逐个字段查找替换

这些模式适用于任何支持跨文件正则搜索的编辑器,例如 VS Code,它把捕获的文本写作 $1。请开启正则模式并区分大小写。与脚本不同,它们作用于每一行,而不只是复选框。

字段 查找 替换为
📅 (?:📅|📆|🗓️?)\s*(\d{4}-\d{2}-\d{2}) [due:: $1]
⏳ (?:⏳|⌛)\s*(\d{4}-\d{2}-\d{2}) [scheduled:: $1]
🛫 🛫\s*(\d{4}-\d{2}-\d{2}) [start:: $1]
➕ ➕\s*(\d{4}-\d{2}-\d{2}) [created:: $1]
✅ ✅\s*(\d{4}-\d{2}-\d{2}) [completion:: $1]
❌ ❌\s*(\d{4}-\d{2}-\d{2}) [cancelled:: $1]
🔺 🔺️? [priority:: highest]
⏫ ⏫️? [priority:: high]
🔼 🔼️? [priority:: medium]
🔽 🔽️? [priority:: low]
⏬ ⏬️? [priority:: lowest]
🏁 🏁\s*(keep|delete) [onCompletion:: $1]
🆔 🆔\s*([A-Za-z0-9_-]+) [id:: $1]
⛔ ⛔\s*([A-Za-z0-9_-]+(?:\s*,\s*[A-Za-z0-9_-]+)*) [dependsOn:: $1]
🔁 🔁\s*([A-Za-z0-9, !]*[A-Za-z0-9])(?=\s*(?:\[|\^|$)) [repeat:: $1]

同样的事,用脚本完成

把它保存为 emoji_to_dataview.py,然后运行 python3 emoji_to_dataview.py Inbox.md Projects/*.md。它只改动复选框行,像 Obsidian Tasks 一样在每个字段前写两个空格,同时也转换备用的截止和计划符号 📆、🗓 和 ⌛。

#!/usr/bin/env python3
"""Rewrite Obsidian Tasks emoji fields to Dataview [key:: value] fields.

Usage: emoji_to_dataview.py NOTE.md [NOTE.md ...]   (rewrites in place; back up first)
Only checkbox lines are touched. Run it on a copy of your vault first.
"""
import os
import re
import sys

DATE = r"(\d{4}-\d{2}-\d{2})"
ID = r"[A-Za-z0-9_-]+"
RULES = [
    (rf"(?:📅|📆|🗓️?)\s*{DATE}", r"[due:: \1]"),
    (rf"(?:⏳|⌛)\s*{DATE}", r"[scheduled:: \1]"),
    (rf"🛫\s*{DATE}", r"[start:: \1]"),
    (rf"➕\s*{DATE}", r"[created:: \1]"),
    (rf"✅\s*{DATE}", r"[completion:: \1]"),
    (rf"❌\s*{DATE}", r"[cancelled:: \1]"),
    (r"🔺️?", "[priority:: highest]"),
    (r"⏫️?", "[priority:: high]"),
    (r"🔼️?", "[priority:: medium]"),
    (r"🔽️?", "[priority:: low]"),
    (r"⏬️?", "[priority:: lowest]"),
    (r"🏁\s*(keep|delete)", r"[onCompletion:: \1]"),
    (rf"🆔\s*({ID})", r"[id:: \1]"),
    (rf"⛔\s*({ID}(?:\s*,\s*{ID})*)", r"[dependsOn:: \1]"),
    # Last, so the rule text ends at the next bracket or the end of the line.
    (r"🔁\s*([A-Za-z0-9, !]*[A-Za-z0-9])(?=\s*(?:\[|\^|$))", r"[repeat:: \1]"),
]
TASK = re.compile(r"^(\s*(?:[-*+]|\d+\.)\s+\[.\]\s)(.*)$")

for path in sys.argv[1:]:
    out = []
    for raw in open(path, encoding="utf-8", newline="").read().split("\n"):
        cr = "\r" if raw.endswith("\r") else ""
        line = raw[: len(raw) - len(cr)]
        m = TASK.match(line)
        if m:
            head, body = m.groups()
            for pattern, repl in RULES:
                body = re.sub(pattern, repl, body)
            body = re.sub(r"\s*(\[(?:due|scheduled|start|created|completion|cancelled|priority|onCompletion|id|dependsOn|repeat):: )", r"  \1", body)
            line = head + body
        out.append(line + cr)
    tmp = path + ".tmp"
    with open(tmp, "w", encoding="utf-8", newline="") as fh:
        fh.write("\n".join(out))
    os.replace(tmp, path)

Dataview 字段转表情符号

反向转换会同时读取 [key:: value] 和 (key:: value),并写出标准表情符号。它不认识的字段,如 [project:: Website],会原样保留在行内;以数字书写的优先级则不动,因为各插件对数字含义的理解并不一致。

之前
- [ ] Send invoice to Acme  [priority:: high]  [repeat:: every month]  [created:: 2026-10-01]  [scheduled:: 2026-11-01]  [due:: 2026-11-03] #finance
- [x] Book flights  [due:: 2026-10-25]  [completion:: 2026-10-20]
- [ ] Order parts  [id:: parts1]
- [ ] Assemble  [start:: 2026-11-10]  [dependsOn:: parts1]  [due:: 2026-11-15]
之后
- [ ] Send invoice to Acme ⏫ 🔁 every month ➕ 2026-10-01 ⏳ 2026-11-01 📅 2026-11-03 #finance
- [x] Book flights 📅 2026-10-25 ✅ 2026-10-20
- [ ] Order parts 🆔 parts1
- [ ] Assemble 🛫 2026-11-10 ⛔ parts1 📅 2026-11-15

对上一个脚本的输出运行它,会原样还原上面的示例行。真实的库没这么整齐:用过 📆、🗓 或 ⌛ 的行会变回 📅 或 ⏳,描述为空的行会多出一个空格。

把它保存为 dataview_to_emoji.py,运行方式相同。

#!/usr/bin/env python3
"""Rewrite Dataview [key:: value] task fields to Obsidian Tasks emoji fields.

Usage: dataview_to_emoji.py NOTE.md [NOTE.md ...]   (rewrites in place; back up first)
Reads [key:: v] and (key:: v). Only checkbox lines are touched.
"""
import os
import re
import sys

DATE = r"(\d{4}-\d{2}-\d{2})"
ID = r"[A-Za-z0-9_-]+"


def field(key, value):
    return rf"[\[(]\s*{key}::\s*{value}\s*[\])]"


RULES = [
    (field("due", DATE), r"📅 \1"),
    (field("scheduled", DATE), r"⏳ \1"),
    (field("start", DATE), r"🛫 \1"),
    (field("created", DATE), r"➕ \1"),
    (field("completion", DATE), r"✅ \1"),
    (field("cancelled", DATE), r"❌ \1"),
    (field("priority", "highest"), "🔺"),
    (field("priority", "high"), "⏫"),
    (field("priority", "medium"), "🔼"),
    (field("priority", "low"), "🔽"),
    (field("priority", "lowest"), "⏬"),
    (field("onCompletion", "(keep|delete)"), r"🏁 \1"),
    (field("id", f"({ID})"), r"🆔 \1"),
    (field("dependsOn", rf"({ID}(?:\s*,\s*{ID})*)"), r"⛔ \1"),
    (field("repeat", r"([A-Za-z0-9, !]*[A-Za-z0-9])"), r"🔁 \1"),
]
TASK = re.compile(r"^(\s*(?:[-*+]|\d+\.)\s+\[.\]\s)(.*)$")

for path in sys.argv[1:]:
    out = []
    for raw in open(path, encoding="utf-8", newline="").read().split("\n"):
        cr = "\r" if raw.endswith("\r") else ""
        line = raw[: len(raw) - len(cr)]
        m = TASK.match(line)
        if m:
            head, body = m.groups()
            for pattern, repl in RULES:
                body = re.sub(pattern, repl, body, flags=re.I if "priority" in pattern else 0)
            line = head + re.sub(r"[ \t]{2,}", " ", body).rstrip()
        out.append(line + cr)
    tmp = path + ".tmp"
    with open(tmp, "w", encoding="utf-8", newline="") as fh:
        fh.write("\n".join(out))
    os.replace(tmp, path)

转换不处理的情况

  • 两个字段之间夹有文字的行。Obsidian Tasks 从行尾开始读,所以本来就没读到这些字段。
  • 行尾的块链接,如 ^abc123。脚本把它留在原位,那正是 Obsidian 期望它所在的位置。
  • Task Genius 独有的语法:日期之后的时间、[#A] 优先级、项目和情境字段、iCalendar 重复规则。这些行请保持原样,或手动转换。
  • 一种格式拼写不同的重复规则。切换后请检查几个重复任务,尤其是带 when done 的。
  • 文件中已经有完成副本的重复任务。这些副本只是普通的已完成任务,和其他行一样转换。
  • 代码块里的行。脚本逐行工作,不认识围栏,所以引用示例的笔记请手动转换。
  • 行尾留有无法识别的字段,如 [custom:: x]。Obsidian Tasks 从行尾开始读,读到这里就停下,前面已转换的字段会被忽略。
  • 🔁 规则后面跟着 #标签。重复规则不会被转换,请把标签移到规则前面,或手动转换这一行。
  • 引用块或标注里的任务(以 > - [ ] 开头的行)。脚本会跳过它们。
  • 日期后面的时间,如 14:30。它留在转换后的字段之外。Obsidian Tasks 没有时间字段,所以只有 Task Genius 能读到。

任务行转任务文件

任务文件是把任务字段放在 YAML frontmatter 里的笔记,也就是 TaskNotes 使用的格式。并不是每个行字段都有对应位置。下面是字段与 TaskNotes 属性的对应关系。

每个字段去哪里了

在任务行中 在任务文件中 说明
📅 2026-11-03
due: 2026-11-03
名称相同。
⏳ 2026-11-01
scheduled: 2026-11-01
名称相同。
🛫 2026-11-10
start: 2026-11-10
TaskNotes 没有开始日期。如果需要,请保留为自定义属性。
➕ 2026-10-01
dateCreated: 2026-10-01T08:00:00Z
写成 ISO 时间戳。
✅ 2026-10-20
completedDate: 2026-10-20
状态变为完成值,日期写入 completedDate。
⏫
priority: high
使用你的 TaskNotes 优先级列表中的值。默认列表只有三级,不是五级,所以要自己决定五级如何对应。
🔁 every month
recurrence: "DTSTART:20261103;FREQ=MONTHLY"
变成 iCalendar 规则。截止日期为 2026-11-03 的 every month 会变成 DTSTART:20261103;FREQ=MONTHLY。
#finance
tags:
  - finance
变成不带井号的 YAML 列表。
🏁 delete
onCompletion: delete
TaskNotes 没有对应属性。保留为 TaskNotes 会忽略的普通 frontmatter 键。
🆔 parts1
id: parts1
TaskNotes 没有对应属性。TaskNotes 里的依赖指向文件路径,而不是 ID。
⛔ parts1
blockedBy:
  - uid: "Tasks/Order parts.md"
    reltype: FINISHTOSTART
变成一个 blockedBy 条目,指向另一个任务的文件。

用 TaskNotes 一次一行

TaskNotes 有 Convert to TaskNote 命令,也可以在功能设置里开启复选框旁的转换按钮。两者都会根据该行创建一个新的任务文件,并把原来的行替换成指向它的链接。几个任务还行,整个库就太慢了。

用 TaskForge 命令行一次转换很多

TaskForge 是一个独立应用,它的命令行工具(也是其 MCP 服务器的引擎)可以在行和文件之间双向转换任务,单个或批量都行。它需要一个已配置好任务文件的库,因为它必须知道如何识别这些文件。请先预览。批量提交时必须重复预览所发现的任务数量,所以范围过宽的查询不会转换超出你本意的任务。

预览,然后转换所有带 #home 标签的任务
taskforge convert --query 'tag:home' --to tasknotes --folder Tasks --dry-run
taskforge convert --query 'tag:home' --to tasknotes --folder Tasks --acknowledge-count 2

一次真实运行

在 Inbox.md 中
- [ ] Send invoice to Acme ⏫ 🔁 every month 📅 2026-11-03 #finance
Tasks/Send invoice to Acme.md
---
title: Send invoice to Acme
status: open
priority: high
tags:
  - finance
  - task
recurrence: "DTSTART:20261103;FREQ=MONTHLY"
due: 2026-11-03
---

⏫ 优先级变成了 high,every month 变成了以截止日期为锚点的 iCalendar 规则。在第二次运行中,开始日期、完成时操作和任务 ID 被保留为普通 frontmatter 键(start、onCompletion、id),而不是被丢弃。命令会拒绝它无法完整转换的任务。把文件转回行时,会在行上加上该文件的标识标签 task。

要反向转换,请指定一篇目标笔记,并先预览:

taskforge convert --query 'tag:home' --to inline --folder Inbox.md --dry-run

大家常问的问题

如何把 Obsidian Tasks 的表情符号转换为 Dataview 格式?

Obsidian Tasks 没有转换命令。请先用正则查找替换或本页这样的脚本改写这些行,然后再到设置里更改任务格式。先备份,并在副本上测试。

在 Obsidian Tasks 中更改任务格式,会转换我现有的任务吗?

不会。这个设置只改变插件此后的读写方式。旧格式的行在你改写之前,不会再被当作字段读取。

如何把很多复选框任务变成 TaskNotes 文件?

TaskNotes 一次转换一行。要批量转换,可以用 TaskForge 命令行转换所有匹配某个查询的任务,并先做一次试运行。无论哪种方式,都请检查没有对应 TaskNotes 属性的字段(如开始日期)是否落在了你想要的位置。

用 Task Genius 的话还需要转换吗?

通常不需要。Task Genius 在同一行里读取表情符号和方括号字段,所以混合的库可以直接使用。只有在你同时使用 Obsidian Tasks 或 Dataview(它们各自只读一种风格)时,转换才有帮助。

脚本会损坏我的笔记吗?

它们只改动复选框行,而且只改动能识别的字段,但会就地改写文件。在把它们用到真实的库之前,请先备份、在副本上运行并做对比。