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) }
关键步骤说明
- 读取原始行:用
bufio.Scanner读取所有原始CSV行,避免csv.Reader剥离引号后丢失原始格式信息。 - 记录引号状态:逐行扫描原始文本,标记每个字段是否原本被双引号包裹,处理转义引号(
"")的场景。 - 空值处理:将空字段替换为
None,同时标记这些字段不需要保留引号。 - 手动控制引号写入:设置
csvWriter.QuotePolicy = csv.QuoteNever禁用自动添加引号,根据之前记录的状态,为需要保留引号的字段手动添加双引号后写入。
测试结果
运行修改后的代码,输出的output.csv与期望一致:
one,"two",three,four 1,"2",3,None 4,None,6,None
内容的提问来源于stack exchange,提问作者npc dump
相关产品推荐
相关产品推荐

