單元 3 · 字串與引號

單/雙引號差異、特殊字元、多行 block/chomping

3.1 三種寫字串的方式

YAML 字串有三種寫法,沒有引號是預設(plain),遇到特殊字元才需要引號:

寫法特性何時用
plain(不引號)最簡潔;:#- 開頭等會出問題一般文字、短值
單引號 '…'照單全收(literal);'' 代表一個引號;不處理跳脫含特殊字元、想原樣顯示
雙引號 "…"支援跳脫 \n\t\"需要換行/跳脫序列時

3.2 何時必須加引號

值的開頭或內容含這些字元時,最好加引號——否則會被誤解成別的東西:

需要引號的特殊值 · QUOTE WHEN
a: "字串: 內含冒號"      # 冒號後跟空格
b: "#不是註解"          # 值開頭是 #
c: "- 開頭像清單"        # 值開頭是 - 
d: "含 前導 空 格"       # 需要保留前導空格
e: "yes,真的"           # 避免被當布林(見單元 5)

3.3 單引號 vs 雙引號

原始碼 · YAML SOURCE
single: '直接顯示 
 不是換行'
double: "這會
換行"
single_q: 'It''s YAML'   # 兩個單引號 = 一個引號
讀出的字串值 · VALUE

single → 直接顯示 不是換行(字面)
double → 這會
換行(真的換行)
single_q → It's YAML

記憶法:單引號 = 照單全收(literal);雙引號 = 會解讀跳脫(escape)。不需要跳脫就別用雙引號——少一層思考。

3.4 多行字串:保留換行 · Block Scalar |

| 表示「換行全都保留」——適合程式碼、shell 指令、日誌。

原始碼 · YAML SOURCE
script: |
  #!/bin/bash
  echo "hello"
  exit 0
讀出的字串值 · VALUE
#!/bin/bash
echo "hello"
exit 0

3.5 多行字串:摺疊換行 · Folded Scalar >

> 表示「軟換行摺疊成空格」——適合長段落、README 描述。

原始碼 · YAML SOURCE
description: >
  這是一段很長的文字,
  在原始檔分成兩行,
  讀出來會變成一行。
讀出的字串值 · VALUE
這是一段很長的文字, 在原始檔分成兩行, 讀出來會變成一行。
縮排規矩:|> 下方的內容縮排要比鍵值對多;縮排不一致會讓解析器把整段視為無效或提前結束。

3.6 修剪後綴 · Chomping

| / > 後面可加一個字元控制結尾換行:

後綴語法 · CHOMPS
keep: |          # 保留所有結尾換行(預設也是保留單一結尾換行)
strip: |-         # 去除所有結尾換行(最後一行沒 
)
clip: |+         # 保留多個結尾換行

實務上 |- 最常用——例如把一段多行字串併進單一環境變數時,不需要結尾換行。

3.7 Worked Example:把一段描述塞進變數

原始碼 · YAML SOURCE
banner: |-
  Welcome to YAML course.
  Learn by doing.
script: |
  npm ci
  npm test
讀出的值 · PARSED

banner"Welcome to YAML course.\nLearn by doing."(無結尾換行)
script"npm ci\nnpm test\n"(保留)

練習:寫一段 | 多行字串存 shell script,再用 |- 寫一段單行摘要,分別用 yq 或 Python 讀出,觀察結尾 \n 差異。

看完這單元你應該能說出:
  • 比較 plain、單引號、雙引號三種寫字串的方式。
  • 列舉哪些特殊字元會逼你加引號(:、#、-、前導空格)。
  • 用 |(保留換行)與 >(摺疊換行)寫多行字串,並說出差別。
  • 用 chomping 後綴(- / +)控制結尾換行。

延伸閱讀


進階真實情境 Worked Example:API OpenAPI 規格片段

OpenAPI(Swagger)規格檔大量使用多行字串描述 API 文件。以下是一段真實場景:

原始碼 · openapi.yaml
paths:
  /users:
    get:
      summary: 取得使用者列表
      description: >
        回傳所有已註冊的使用者。
        支援分頁參數 pagelimit。
        預設每頁回傳 20 筆。
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
          description: '# 頁碼從 1 開始'
拆解 · ANATOMY

> 摺疊多行:三行文字合併為一段落
'# 頁碼從 1 開始':單引號讓 # 不被當註解
default: 1 整數不加引號
④ flow [ ] 在其他場景更常見,此處全用 block

為什麼這樣設計而非替代方案:description> 而非 | 是因為 API 文件是一段連續文字,不需要保留換行;如果用 | 每行會帶 \n,在 Swagger UI 中渲染會有額外空行。用單引號包住 '# 頁碼…' 是因為值開頭是 #,不加引號會被當註解。

深入原理擴充

Folded Scalar 的換行規則

> 摺疊_scalar 預設(clip)行為:空行會被保留為 \n\n,而連續行之間的換行會被替換成空格。但有個例外:如果下一行的縮排與首行相同,且中間沒有空行,那換行會被摺疊成空格;如果中間有空行(blank line),則那個空行會被保留。這意味著 > 的結果不是「全部連成一行」,而是「段落內換行摺疊、段落間保留空行」。

大家以為是這樣但其實不是……

誤解:「單引號內不能有換行」——其實 YAML 的單引號字串在 block context 中完全可以跨行,跨行時的換行會被保留為字面內容。差別只在於:雙引號內的 \n 會被解讀為換行跳脫,而單引號內的 \n 只是兩個字元 \n

診斷式疑難排解表

症狀可能原因解決方案
值開頭的 # 被吃掉plain string 開頭是 # 被當註解加引號 "#..."'#...'
| 多行字串結尾多一個換行預設 chomping 保留一個結尾 \n|- 去掉結尾換行
> 結果出現意外空行源檔中有空行(blank line),> 保留段落間空行移除源檔空行,或改用 | + 手動控制
多行區塊內容被提前截斷內容縮排不比鍵多確保 | / > 下方所有行的縮排 ≥ 鍵的縮排 + 1
雙引號內的 \n 顯示為字面 \n可能用了單引號而非雙引號雙引號才支援跳脫序列

進階挑戰題

  1. 三種引號實驗:用 plain、單引號、雙引號分別定義同一個值 Hello "World",用 yaml.safe_load 讀出後比對三者結果。哪種寫法最簡潔?哪種最明確?
  2. Chomping 組合:同一段多行文字,分別用 ||-|+ 定義,用 Python 印出 repr() 比較三者的結尾換行差異。
  3. 真實 bug 重現:在 YAML 中寫 key: This is a # comment(值中間含 #),解析後值是什麼?再寫 key: "#hashtag",解析後又是什麼?解釋差異的原因。

① 專案級端到端 Worked Example:GitHub Actions 多步驟部署 workflow

字串技巧(plain/引號/多行 block scalar)在 CI workflow 中最密集——run: 步驟幾乎都是多行 shell 腳本。以下是完整的部署專案:

產出檔案樹

deploy-app/
├── .github/
│   └── workflows/
│       └── deploy.yml      # 主 workflow(多行字串大本營)
├── scripts/
│   ├── build.sh            # 被 YAML 多行字串呼叫的腳本
│   └── notify.sh           # 部署後通知
└── README.md

關鍵檔案內容

.github/workflows/deploy.yml
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,換行與縮排的正確性是部署成敗的關鍵。

② 效能/品質/安全深度

品質:字串是最常被低估的 bug 來源

字串在 YAML 中的問題不是「打錯字」而是隱形的語意差異on 變布林、010 變八進位、"1.0"1.0 行為不同。品質策略:

安全:多行字串 = shell 注入面

安全提醒:run: | 的內容最後都會送進 /bin/bash -e 執行。如果腳本內插值來自不可信來源(PR 標題、issue body、外部 API),就可能被注入 ;$(...)、反引號。規則:永遠用 "$VAR" 引號包住所有變數,參數化而非字串拼接

效能:多行字串對解析的影響

| / > block scalar 在大檔案中比一行行引號字串解析更快、佔用更少——因為解析器只需處理一次「區塊頭」,不必逐行做引號掃描。實務上大量腳本內容的 workflow,用 block scalar 也能讓檔案瘦身 30% 以上。

③ 站際比較對照表

面向yamlmarkdownjsongithubgitlab
字串寫法plain / 單引號 / 雙引號三種純文字,無引號概念雙引號唯一Markdown 為主,YAML 在 workflowYAML 在 pipeline,字串靠引號
多行字串|(保留換行)/ >(摺疊)直接換行就是多行\n 跳脫workflow 用 | 寫 shellpipeline 用 | 寫 script
跳脫語意單引號不跳脫、雙引號會跳脫靠 HTML 實體(&)\n \t${{ }} 是平台級插值$VAR / ${VAR} 插值
引號陷阱主題「值開頭 # / - / 冒號」要加引號特殊字元用跳脫字串內引號需跳脫 \"expression 混字串需小心變數空值 / 型別陷阱

④ 互動式檢核清單