You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言解析CSV文件时如何保留原始双引号?

解决Go解析CSV时保留原始双引号的问题

Go标准库encoding/csv的csv.Reader会自动剥离CSV字段两端的双引号——这是符合CSV规范的行为,因为引号是用来界定包含特殊字符(如逗号、换行)的字段的语法标记,而非字段内容的一部分。要保留原始的引号,需要额外记录哪些字段原本被引号包裹,写入时再为这些字段手动添加引号。

修改后的完整代码

package main

import (
	"bufio"
	"encoding/csv"
	"log"
	"os"
	"strings"
)

func processCSV(inputFile, outputFile string) {
	// 读取原始文件的所有行,用于判断字段是否带引号
	inputFilePtr, err := os.Open(inputFile)
	if err != nil {
		log.Fatalf("Error opening input file: %v", err)
	}
	defer inputFilePtr.Close()

	var lines []string
	scanner := bufio.NewScanner(inputFilePtr)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		log.Fatalf("Error reading input file lines: %v", err)
	}

	// 解析CSV内容,同时记录每个字段是否需要保留引号
	csvReader := csv.NewReader(strings.NewReader(""))
	csvReader.Comma = ','
	csvReader.LazyQuotes = true

	var records [][]string
	var shouldQuote [][]bool

	for _, line := range lines {
		csvReader.Reset(strings.NewReader(line))
		record, err := csvReader.Read()
		if err != nil {
			log.Fatalf("Error parsing CSV line: %v", err)
		}
		records = append(records, record)

		// 判断当前行每个字段是否原始带引号
		quoteStatus := make([]bool, len(record))
		pos := 0
		lineLen := len(line)
		for i := range record {
			// 跳过前置的分隔符
			for pos < lineLen && line[pos] == csvReader.Comma {
				pos++
			}
			if pos >= lineLen {
				quoteStatus[i] = false
				continue
			}

			hasQuote := false
			if line[pos] == '"' {
				hasQuote = true
				pos++
				// 跳过字段内容,处理转义的引号("")
				for pos < lineLen {
					if line[pos] == '"' {
						pos++
						if pos < lineLen && line[pos] == '"' {
							pos++
						} else {
							break
						}
					} else {
						pos++
					}
				}
			} else {
				// 非引号包裹的字段,直接跳到下一个分隔符
				for pos < lineLen && line[pos] != csvReader.Comma {
					pos++
				}
			}
			quoteStatus[i] = hasQuote
		}
		shouldQuote = append(shouldQuote, quoteStatus)
	}

	// 处理空值为"None"
	for i, record := range records {
		for j, value := range record {
			if value == "" {
				records[i][j] = "None"
				shouldQuote[i][j] = false // 替换后的字段不需要引号
			}
		}
	}

	// 写入处理后的CSV
	outputFilePtr, err := os.Create(outputFile)
	if err != nil {
		log.Fatalf("Error creating output file: %v", err)
	}
	defer outputFilePtr.Close()

	csvWriter := csv.NewWriter(outputFilePtr)
	defer csvWriter.Flush()
	// 禁用自动引号,手动控制哪些字段加引号
	csvWriter.QuotePolicy = csv.QuoteNever

	for i, record := range records {
		outputRecord := make([]string, len(record))
		for j, value := range record {
			if shouldQuote[i][j] {
				// 手动添加引号,若内容含引号需转义(本例无此场景)
				outputRecord[j] = "\"" + value + "\""
			} else {
				outputRecord[j] = value
			}
		}
		if err := csvWriter.Write(outputRecord); err != nil {
			log.Fatalf("Error writing to output file: %v", err)
		}
	}
}

func main() {
	inputFileName := "input.csv"
	outputFileName := "output.csv"
	processCSV(inputFileName, outputFileName)
}

关键步骤说明

  1. 读取原始行:用bufio.Scanner读取所有原始CSV行,避免csv.Reader剥离引号后丢失原始格式信息。
  2. 记录引号状态:逐行扫描原始文本,标记每个字段是否原本被双引号包裹,处理转义引号("")的场景。
  3. 空值处理:将空字段替换为None,同时标记这些字段不需要保留引号。
  4. 手动控制引号写入:设置csvWriter.QuotePolicy = csv.QuoteNever禁用自动添加引号,根据之前记录的状态,为需要保留引号的字段手动添加双引号后写入。

测试结果

运行修改后的代码,输出的output.csv与期望一致:

one,"two",three,four
1,"2",3,None
4,None,6,None

内容的提问来源于stack exchange,提问作者npc dump

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:05:19