可通过设置 Reader.Comma 和 Writer.Comma 字段(rune 类型)自定义分隔符,如 '|' 或 '\t',须在首次 Read()/Write() 前完成;Quote 和 QuoteEscape 可修改引号及转义逻辑,但需避免 Comma 与 Quote 相同,且 Quote 不能为 ", \r, \n。

如何设置 csv.Reader 和 csv.Writer 的分隔符
Go 标准库的 encoding/csv 默认用逗号分隔、双引号包裹字段,但可以通过修改 Reader.Comma 和 Writer.Comma 字段来切换分隔符。注意:必须在首次调用 Read() 或 Write() 之前设置,否则无效。
常见场景包括读写制表符(\t)、竖线(|)或分号(;)分隔的 CSV(常用于 Excel 导出或某些数据库导出格式)。
-
Reader.Comma和Writer.Comma类型是rune,不能设为字符串(比如"|"),只能是单字符,例如'|'或'\t' - 如果传入多字节 rune(如中文标点),运行时不会报错,但行为未定义,实际解析可能失败
- 分隔符不能是引号(
")、回车(\r)或换行(\n),否则Reader.Read()会返回csv.ParseError,错误信息类似"illegal comma"
如何自定义引号字符和转义逻辑
csv.Reader 和 csv.Writer 都有 Quote 字段,默认为 ''(ASCII 双引号)。你可以把它改成其他字符,比如单引号 '\',但要注意:标准 CSV 规范只定义了双引号语义,改用其他字符后,其他工具(如 Excel、pandas)很可能无法正确识别。
更关键的是 QuoteEscape 字段 —— 它控制「当字段内出现引号字符时,如何转义」。默认是双引号本身(''),即 "a""b" 表示字段值为 a"b。如果你把 Quote 改成单引号,通常也应同步把 QuoteEscape 改成单引号,否则解析会出错。
使用 qbo-mileage CLI 及用户凭证,从 Airtable、Outlook 或 Google Calendar 记录生成 QuickBooks Online 里程 CSV 文件。
- 若
Quote == 0(即禁用引号),则QuoteEscape被忽略,所有字段都不加引号;但如果字段含分隔符、换行或引号,Writer会 panic 并提示"field contains newline"等错误 -
Reader在Quote == 0时仍能读取无引号数据,但遇到含分隔符的字段会直接截断 —— 它不会尝试“猜”字段边界 - 不要把
Quote和Comma设为同一个字符,否则Reader无法区分分隔符和字段内容,会报"bare quote"或"invalid quoted field"
读取含混合引号/空格/换行的真实 CSV 文件
真实 CSV 往往包含带换行的字段(如多行地址)、前后空格、甚至 BOM 头。标准 csv.Reader 不自动 trim 空格,也不跳过 BOM —— 这些都得手动处理。
- 读取前先用
bytes.TrimPrefix(buf, []byte("\xef\xbb\xbf"))去除 UTF-8 BOM(如果源文件带 BOM) - 若需自动 trim 字段首尾空白,不能依赖
Reader,而应在Read()后对每个[]string元素手动调用strings.TrimSpace() - 字段内换行是合法的,但前提是该字段被引号包裹;否则
Reader会认为那是新记录的开始,导致numFields错误或io.ErrUnexpectedEOF - 若原始数据用 Windows 换行(
\r\n),且字段内也有\r\n,只要外层有引号,Reader能正确处理;但若文件末尾缺换行符,某些版本 Go 的Reader可能漏掉最后一行(Go 1.20+ 已修复)
一个安全的自定义 CSV 读写小封装
直接操作 Reader/Writer 字段容易遗漏初始化顺序或边界检查。建议封装一个工厂函数,统一约束参数合法性:
func NewCSVReader(r io.Reader, opts ...func(*csv.Reader)) *csv.Reader {
reader := csv.NewReader(r)
reader.Comma = ','
reader.FieldsPerRecord = -1 // 允许每行字段数不同
for _, opt := range opts {
opt(reader)
}
return reader
}
// 使用示例:读取竖线分隔、单引号包裹的文件
r := NewCSVReader(file, func(cr *csv.Reader) {
cr.Comma = '|'
cr.Quote = '\''
cr.QuoteEscape = '\''
})
这种封装能避免忘记设 Comma 就调用 Read(),也能集中处理常见陷阱(比如禁止 Comma == Quote)。但别试图在封装里自动 trim 或猜测编码 —— 那会让行为不透明,调试时反而更难定位问题。
最易被忽略的一点:无论怎么改 Comma 或 Quote,csv.Reader 永远按「逐行 + 引号配对」的方式切分记录,它不支持跨多行的注释、跳过空行或条件解析。这些逻辑必须由你在外层控制。

















