在不同格式之間轉換 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(它們各自只讀一種風格)時,轉換才有幫助。
指令碼會損壞我的筆記嗎?
它們只改動核取方塊行,而且只改動能識別的欄位,但會就地改寫檔案。在把它們用到真實的庫之前,請先備份、在副本上執行並做對比。