在不同格式之间转换 Obsidian 任务
Obsidian Tasks 没有把整个库从一种格式改写成另一种的命令,它的文档也是这么说的。下面的转换是能完成这件事的脚本和方案,每一个都用该插件自己文档中的示例行测试过,另外还有一种已有工具的转换:任务行转任务文件,以及反向转换。
基于 Obsidian Tasks 8.6、Task Genius 9.13 和 TaskNotes 4.14 编写,2026 年 10 月。字段名和符号列在速查表里。
动手之前
- 备份库. 复制文件夹,或提交到 git。一个要改写 4,000 行的脚本,应当很容易撤销。
- 先在副本上操作. 在库的副本上运行转换,用 Obsidian 打开,再运行一个你常用的任务查询。把数量与原库对比。
- 先转换,再切换插件. Obsidian Tasks 一次只读取一种格式。先转换这些行,再到设置里更改任务格式,这样插件就不会看到转换到一半的库。
- 检查剩下的内容. 在库里搜索旧符号(表情符号用 📅、⏳、⏫;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 | | 名称相同。 |
⏳ 2026-11-01 | | 名称相同。 |
🛫 2026-11-10 | | TaskNotes 没有开始日期。如果需要,请保留为自定义属性。 |
➕ 2026-10-01 | | 写成 ISO 时间戳。 |
✅ 2026-10-20 | | 状态变为完成值,日期写入 completedDate。 |
⏫ | | 使用你的 TaskNotes 优先级列表中的值。默认列表只有三级,不是五级,所以要自己决定五级如何对应。 |
🔁 every month | | 变成 iCalendar 规则。截止日期为 2026-11-03 的 every month 会变成 DTSTART:20261103;FREQ=MONTHLY。 |
#finance | | 变成不带井号的 YAML 列表。 |
🏁 delete | | TaskNotes 没有对应属性。保留为 TaskNotes 会忽略的普通 frontmatter 键。 |
🆔 parts1 | | TaskNotes 没有对应属性。TaskNotes 里的依赖指向文件路径,而不是 ID。 |
⛔ parts1 | | 变成一个 blockedBy 条目,指向另一个任务的文件。 |
用 TaskNotes 一次一行
TaskNotes 有 Convert to TaskNote 命令,也可以在功能设置里开启复选框旁的转换按钮。两者都会根据该行创建一个新的任务文件,并把原来的行替换成指向它的链接。几个任务还行,整个库就太慢了。
用 TaskForge 命令行一次转换很多
TaskForge 是一个独立应用,它的命令行工具(也是其 MCP 服务器的引擎)可以在行和文件之间双向转换任务,单个或批量都行。它需要一个已配置好任务文件的库,因为它必须知道如何识别这些文件。请先预览。批量提交时必须重复预览所发现的任务数量,所以范围过宽的查询不会转换超出你本意的任务。
taskforge convert --query 'tag:home' --to tasknotes --folder Tasks --dry-run
taskforge convert --query 'tag:home' --to tasknotes --folder Tasks --acknowledge-count 2 一次真实运行
- [ ] Send invoice to Acme ⏫ 🔁 every month 📅 2026-11-03 #finance ---
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(它们各自只读一种风格)时,转换才有帮助。
脚本会损坏我的笔记吗?
它们只改动复选框行,而且只改动能识别的字段,但会就地改写文件。在把它们用到真实的库之前,请先备份、在副本上运行并做对比。