單/雙引號差異、特殊字元、多行 block/chomping
YAML 字串有三種寫法,沒有引號是預設(plain),遇到特殊字元才需要引號:
| 寫法 | 特性 | 何時用 |
|---|---|---|
| plain(不引號) | 最簡潔;:、#、- 開頭等會出問題 | 一般文字、短值 |
單引號 '…' | 照單全收(literal);'' 代表一個引號;不處理跳脫 | 含特殊字元、想原樣顯示 |
雙引號 "…" | 支援跳脫 \n、\t、\" 等 | 需要換行/跳脫序列時 |
值的開頭或內容含這些字元時,最好加引號——否則會被誤解成別的東西:
a: "字串: 內含冒號" # 冒號後跟空格 b: "#不是註解" # 值開頭是 # c: "- 開頭像清單" # 值開頭是 - d: "含 前導 空 格" # 需要保留前導空格 e: "yes,真的" # 避免被當布林(見單元 5)
single: '直接顯示 不是換行' double: "這會 換行" single_q: 'It''s YAML' # 兩個單引號 = 一個引號
single → 直接顯示
不是換行(字面)double → 這會
換行(真的換行)single_q → It's YAML
用 | 表示「換行全都保留」——適合程式碼、shell 指令、日誌。
script: | #!/bin/bash echo "hello" exit 0
#!/bin/bash echo "hello" exit 0
用 > 表示「軟換行摺疊成空格」——適合長段落、README 描述。
description: > 這是一段很長的文字, 在原始檔分成兩行, 讀出來會變成一行。
這是一段很長的文字, 在原始檔分成兩行, 讀出來會變成一行。
| 與 > 下方的內容縮排要比鍵值對多;縮排不一致會讓解析器把整段視為無效或提前結束。| / > 後面可加一個字元控制結尾換行:
keep: | # 保留所有結尾換行(預設也是保留單一結尾換行) strip: |- # 去除所有結尾換行(最後一行沒 ) clip: |+ # 保留多個結尾換行
實務上 |- 最常用——例如把一段多行字串併進單一環境變數時,不需要結尾換行。
banner: |- Welcome to YAML course. Learn by doing. script: | npm ci npm test
banner → "Welcome to YAML course.\nLearn by doing."(無結尾換行)script → "npm ci\nnpm test\n"(保留)
練習:寫一段 | 多行字串存 shell script,再用 |- 寫一段單行摘要,分別用 yq 或 Python 讀出,觀察結尾 \n 差異。
OpenAPI(Swagger)規格檔大量使用多行字串描述 API 文件。以下是一段真實場景:
paths: /users: get: summary: 取得使用者列表 description: > 回傳所有已註冊的使用者。 支援分頁參數page與limit。 預設每頁回傳 20 筆。 parameters: - name: page in: query schema: type: integer default: 1 description: '# 頁碼從 1 開始'
① > 摺疊多行:三行文字合併為一段落
② '# 頁碼從 1 開始':單引號讓 # 不被當註解
③ default: 1 整數不加引號
④ flow [ ] 在其他場景更常見,此處全用 block
為什麼這樣設計而非替代方案:description 用 > 而非 | 是因為 API 文件是一段連續文字,不需要保留換行;如果用 | 每行會帶 \n,在 Swagger UI 中渲染會有額外空行。用單引號包住 '# 頁碼…' 是因為值開頭是 #,不加引號會被當註解。
> 摺疊_scalar 預設(clip)行為:空行會被保留為 \n\n,而連續行之間的換行會被替換成空格。但有個例外:如果下一行的縮排與首行相同,且中間沒有空行,那換行會被摺疊成空格;如果中間有空行(blank line),則那個空行會被保留。這意味著 > 的結果不是「全部連成一行」,而是「段落內換行摺疊、段落間保留空行」。
\n 會被解讀為換行跳脫,而單引號內的 \n 只是兩個字元 \ 和 n。| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
值開頭的 # 被吃掉 | plain string 開頭是 # 被當註解 | 加引號 "#..." 或 '#...' |
| 多行字串結尾多一個換行 | 預設 chomping 保留一個結尾 \n | 用 |- 去掉結尾換行 |
> 結果出現意外空行 | 源檔中有空行(blank line),> 保留段落間空行 | 移除源檔空行,或改用 | + 手動控制 |
| 多行區塊內容被提前截斷 | 內容縮排不比鍵多 | 確保 | / > 下方所有行的縮排 ≥ 鍵的縮排 + 1 |
雙引號內的 \n 顯示為字面 \n | 可能用了單引號而非雙引號 | 雙引號才支援跳脫序列 |
Hello "World",用 yaml.safe_load 讀出後比對三者結果。哪種寫法最簡潔?哪種最明確?|、|-、|+ 定義,用 Python 印出 repr() 比較三者的結尾換行差異。key: This is a # comment(值中間含 #),解析後值是什麼?再寫 key: "#hashtag",解析後又是什麼?解釋差異的原因。字串技巧(plain/引號/多行 block scalar)在 CI workflow 中最密集——run: 步驟幾乎都是多行 shell 腳本。以下是完整的部署專案:
deploy-app/ ├── .github/ │ └── workflows/ │ └── deploy.yml # 主 workflow(多行字串大本營) ├── scripts/ │ ├── build.sh # 被 YAML 多行字串呼叫的腳本 │ └── notify.sh # 部署後通知 └── README.md
name: Deploy on: workflow_dispatch: {} push: {tags: ["v*"]} jobs: deploy: runs-on: ubuntu-latest env: VERSION: "${{ github.ref_name }}" REGION: ap-northeast-1 steps: - uses: actions/checkout@v4 - name: 建立並推送映像 run: | docker build -t myapp:${VERSION} . docker push registry.example.com/myapp:${VERSION} - name: 部署到 K8s run: | kubectl set image deployment/app app=myapp:${VERSION} kubectl rollout status deployment/app --timeout=120s - name: 通知 run: bash scripts/notify.sh "${{ github.ref_name }}" "${{ secrets.WEBHOOK }}"
$ actionlint .github/workflows/deploy.yml && echo OK OK $ yamllint -d relaxed .github/workflows/deploy.yml (無輸出 = 通過) $ act -n # 本地乾跑(dry-run)解析 workflow [Deploy/deploy] 🚀 Start image=... platform=... [Deploy/deploy] ✅ Success
① run: |:多行 shell 腳本,換行保留——每行就是一條指令
② "v*":glob pattern 用引號,避免 * 被特殊處理
③ "${{ github.ref_name }}":GitHub expression 包在雙引號內,確保是字串
④ "${{ secrets.WEBHOOK }}":secret 從環境注入,不寫進 YAML
⑤ REGION plain 字串——不含特殊字元,不需要引號
端到端流程:打 tag v1.2.3 → workflow 觸發 → 多行 run 建映像 → 部署 K8s → 通知。每個 | 區塊都是一段完整的 shell script,換行與縮排的正確性是部署成敗的關鍵。
字串在 YAML 中的問題不是「打錯字」而是隱形的語意差異:on 變布林、010 變八進位、"1.0" 與 1.0 行為不同。品質策略:
| / >,不要用 \n 硬拼。actionlint / yamllint 攔截常見字串語法錯誤。run: | 的內容最後都會送進 /bin/bash -e 執行。如果腳本內插值來自不可信來源(PR 標題、issue body、外部 API),就可能被注入 ;、$(...)、反引號。規則:永遠用 "$VAR" 引號包住所有變數,參數化而非字串拼接。| / > block scalar 在大檔案中比一行行引號字串解析更快、佔用更少——因為解析器只需處理一次「區塊頭」,不必逐行做引號掃描。實務上大量腳本內容的 workflow,用 block scalar 也能讓檔案瘦身 30% 以上。
| 面向 | yaml | markdown | json | github | gitlab |
|---|---|---|---|---|---|
| 字串寫法 | plain / 單引號 / 雙引號三種 | 純文字,無引號概念 | 雙引號唯一 | Markdown 為主,YAML 在 workflow | YAML 在 pipeline,字串靠引號 |
| 多行字串 | |(保留換行)/ >(摺疊) | 直接換行就是多行 | \n 跳脫 | workflow 用 | 寫 shell | pipeline 用 | 寫 script |
| 跳脫語意 | 單引號不跳脫、雙引號會跳脫 | 靠 HTML 實體(&) | \n \t 等 | ${{ }} 是平台級插值 | $VAR / ${VAR} 插值 |
| 引號陷阱主題 | 「值開頭 # / - / 冒號」要加引號 | 特殊字元用跳脫 | 字串內引號需跳脫 \" | expression 混字串需小心 | 變數空值 / 型別陷阱 |
| / |- / |+ / > 的 YAML,並預測每種的結尾換行行為。# 被當註解」「值含 : 被誤判」的實際報錯。run: 步驟,並用 actionlint 驗證。"$VAR" 包住來自 YAML 的變數,避免注入。