跳至主要內容
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(它們各自只讀一種風格)時,轉換才有幫助。

指令碼會損壞我的筆記嗎?

它們只改動核取方塊行,而且只改動能識別的欄位,但會就地改寫檔案。在把它們用到真實的庫之前,請先備份、在副本上執行並做對比。