# 分隔符模式(Sep Pattern)
本文档介绍 WPL 中 `{...}` 分隔符模式语法,用于在字段分隔位置使用通配符和特殊匹配进行灵活切分。
适用于快捷分隔符(`\,`、`\s` 等)无法满足的复杂场景,例如"跳过任意内容直到关键字""空白 + 特定前缀"等。
提示:如果你的分隔需求可以用固定字符串满足,优先使用快捷分隔符(参考 [WPL 基础 - 分隔符优先级与合并](./01-wpl_basics.md#分隔符优先级与合并sep))。
---
## 📚 文档导航
| [**基本语法**](#基本语法) | `{...}` 写法、通配符、转义字符 |
| [**语法表**](#语法表) | 所有支持的记法一览 |
| [**通配符 `*` 与 `?`**](#通配符--与-) | 非贪婪匹配、单字符匹配 |
| [**空白匹配**](#空白匹配-s-与-h) | `\s` 连续空白、`\h` 水平空白 |
| [**保留标记 `()`**](#保留标记-preserve) | 匹配但不消费,留给下一阶段 |
| [**使用约束**](#使用约束) | `*` 和 `()` 的限制 |
| [**实战示例**](#实战示例) | 常见场景与完整规则 |
---
## 基本语法
在字段的分隔符位置使用花括号 `{...}` 包裹模式表达式:
```wpl
# 快捷分隔符
chars\,
# 模式分隔符
chars{*=}
```
模式分隔符与快捷分隔符遵循相同的优先级规则(字段级 > 组级 > 上游)。
---
## 语法表
| `a` | 字面字符 `a` | `{abc}` 匹配 `"abc"` |
| `*` | 零或多个任意字符(**非贪婪**,最短匹配) | `{*=}` 匹配到第一个 `=` |
| `?` | 恰好一个任意字符 | `{field?:}` 匹配 `"fieldA:"` |
| `\0` | 空字节 | |
| `\n` | 换行 | |
| `\t` | 制表符 | |
| `\r` | 回车 | |
| `\s` | 一个或多个连续空白 `[ \t\r\n]+` | `{\s=}` 跳过空白后匹配 `=` |
| `\h` | 一个或多个水平空白 `[ \t]+` | `{\h:\h}` 匹配 `" : "` |
| `\\` `\*` `\?` `\{` `\}` `\(` `\)` | 字面转义 | `{\*}` 匹配字面 `*` |
| `(...)` | 匹配但不消费(仅限模式末尾) | `{*(key=)}` 保留 `key=` |
> 注意:`\s` 和 `\h` 在 `{}` 模式内部匹配**一个或多个**连续空白;`{}` 之外的 `\s` 沿用原有语义,旧配置不受影响。
---
## 通配符 `*` 与 `?`
### `*` — 非贪婪匹配
`*` 匹配零或多个任意字符,采用**最短匹配**策略。
```
输入:a=b=c
模式:{*=}
匹配:a= ← 找到第一个 "=" 即停止,而非 "a=b="
```
**典型场景:** 消费到某个关键字符。
```wpl
# 匹配到第一个等号
chars{*=}
# 匹配到第一个冒号加空格
chars{*:\s}
```
### `?` — 单字符匹配
`?` 恰好匹配一个任意字符。
```
输入:field1: value
模式:{field?:\s}
匹配:field1: ← "?" 匹配了 "1"
```
**约束:** 模式中 `*` 最多出现**一次**,超过则报配置错误。`?` 无数量限制。
---
## 空白匹配 `\s` 与 `\h`
### `\s` — 连续空白
匹配一个或多个空白字符(空格、制表符、回车、换行)。
```
输入:key =value
模式:{\s=}
匹配: = ← 三个空格 + "="
```
### `\h` — 水平空白
匹配一个或多个水平空白字符(仅空格和制表符,不含换行)。
```
输入:name : value
模式:{\h:\h}
匹配: : ← 制表/空格 + ":" + 制表/空格
```
### `\S` 与 `\H`
与上面相反:
| `\S` | 一个或多个连续**非空白**字符 |
| `\H` | 一个或多个连续**非水平空白**字符 |
---
## 保留标记(Preserve)
`()` 包裹的内容参与匹配以确认分隔位置,但**不会从输入流中消费**。下一阶段从 `()` 内容的起始处继续读取。
```
输入:hello key=value
模式:{*\s(key=)}
消费:hello ← 被截断,作为当前字段的值
保留:key=value ← 留在输入流中,下一个字段从这里开始
```
### 使用方式
```wpl
# 消费到 "command=" 之前,保留 "command=" 给下一个字段
chars{*(command=)}
# 消费任意内容 + 空白区域,保留 "next" 给下一个字段
chars{*\s(next)}
```
### 约束
- `()` 只能出现在模式**末尾**。`{*(key=)}` 合法,`{(key)*=}` 不合法。
- `()` 不允许嵌套。
- `()` 内允许字面量、`\s` `\h` `\0` `\n` `\t` `\r`、`?` 及转义字符。
- `()` 内**不允许 `*`**——保留部分必须是确定长度。
---
## 使用约束
| `*` 最多一个 | 整个模式中 `*` 最多出现一次,多于一个报错 |
| `()` 仅末尾 | 保留标记只能出现在模式的最后 |
| `()` 内无 `*` | 保留段不允许不确定长度的通配 |
| `()` 无嵌套 | 不允许 `((...))` |
| 不可与 ups_val 混用 | 同时配置 `{}` 与次级结束符时,解析阶段报错 |
---
## 实战示例
### 场景 1:Key=Value 风格日志
日志格式中,字段以 `key=value` 排列,每个 value 后面跟空白和下一个 key。
```
输入:src=192.168.1.1 dst=10.0.0.1 action=accept proto=TCP
```
```wpl
rule kv_log {
(
ip{*\s(dst=)}:src,
ip{*\s(action=)}:dst,
chars{*\s(proto=)}:action,
chars:proto
)
}
# 输出:src=192.168.1.1, dst=10.0.0.1, action=accept, proto=TCP
```
说明:每个字段使用 `{*\s(next_key=)}` 消费到下一个 key 之前的空白,同时保留下一个 key 给后续字段。
### 场景 2:分隔符中包含空白
日志中字段之间用 ` | `(空格 + 竖线 + 空格)分隔。
```
```wpl
rule pipe_sep {
(ip:client, chars:user, time:ts){\h|\h}
}
# 输出:client=192.168.1.1, user=admin, ts=2024-01-01 10:00:00
```
### 场景 3:匹配到特定关键字
消费到 `command=` 出现的位置。
```
输入:user=admin role=root command=ls -la
```
```wpl
rule cmd_log {
(
chars{*(command=)}:prefix,
chars:command
)
}
# prefix = "user=admin role=root "
# command = "ls -la"(前面 "command=" 被 consume_sep 消费)
```
### 场景 4:字段名后跟变化字符
某些日志的字段名格式为 `fieldN:`,N 是一个可变字符。
```
输入:field1: hello field2: world
```
```wpl
rule field_var {
(chars{field?:\s}:f1, chars:f2)
}
# f1 = "hello", f2 = "world"
```
### 场景 5:纯字面量模式
当 `{}` 内不含通配符时,等价于快捷字面量分隔符,但可以表达多字符序列。
```wpl
# 以下两种写法效果相同
chars{::}
# 等价于
chars\:\:
```
### 场景 6:结合管道函数
模式分隔符可与字段级管道组合使用。
```wpl
rule combined {
(
chars{*\s(src=)}:header,
kvarr(\s):payload
```
---
## 与快捷分隔符的对比
| 语法 | `\,` `\;` `\s` 等 | `{...}` |
| 匹配能力 | 固定字符串 | 通配符、空白区域、保留标记 |
| 性能 | 最优(memchr) | 接近(纯字面退化为 memchr) |
| 适用场景 | 分隔符是确定字符 | 分隔逻辑含变化部分 |
| 优先级 | 与字段/组级一致 | 与字段/组级一致 |
**选择建议:**
- 分隔符是固定字符(逗号、空格、竖线等)→ 使用快捷分隔符
- 分隔符包含空白区域、需要跳过到关键字、需要保留部分内容 → 使用模式分隔符
---
## 常见问题
### Q: `{...}` 内的 `\s` 和外面的 `\s` 一样吗?
不完全一样。`{}` 内的 `\s` 匹配**一个或多个**连续空白字符;`{}` 外面的 `\s` 沿用原有语义(表示空格分隔符)。旧配置不受影响。
### Q: 可以在组级使用模式分隔符吗?
可以。与快捷分隔符一样,模式分隔符支持字段级和组级:
```wpl
# 字段级
(chars{*=}, digit)
# 组级
(chars, digit){*=}
```
### Q: `*` 匹配到行尾怎么办?
如果 `*` 后续没有其他匹配内容,`*` 会匹配到输入的末尾。如果没找到后续内容的匹配,则该模式整体不匹配,字段将读取到行尾。
### Q: 可以只用 `()` 不带 `*` 吗?
可以。`()` 不依赖 `*`,例如 `{\s(key=)}` 表示:匹配空白区域后确认 `key=` 存在,消费空白部分,保留 `key=`。
---
## 相关资源
- WPL 基础:[01-wpl_basics.md](./01-wpl_basics.md) — 字段、分组、分隔符基础
- 核心概念:[02-core-concepts.md](./02-core-concepts.md) — 分隔符优先级详解
- 管道函数:[03-wpl_pipe_functions.md](./03-wpl_pipe_functions.md) — 字段级管道
- 语言参考:[04-language-reference.md](./04-language-reference.md) — 完整类型列表