最近业务中涉及到了 csv 文件的读写, 本以为是非常简单的一件事情. 结果却踩了几个坑. 想象一下下面这段写 csv 文件的代码有什么问题?
- $arr = [ ["this is a string", "another string", "Hello\nWorld", "你好, 世界"],
- [".....", ".....","xxx","111"]
- ];
- $filepath = "/home/zhangsan/output.csv";
- foreach ($arr as $key => $val) {
- file_put_contents($filepath, implode(",", $val) . "\n", FILE_APPEND);
- }
代码功能很简单, 就是把一个字符串二维数组写入 csv 文件中存储. 正常情况下还好, 然后二维数组中如果有的字符串里面本来就有换行符或者逗号, 瞬间懵逼. 于是去认真调研了一把 csv 文件格式, 分享给大家.
无论是平时办公还是网络传输, csv(Comma Separated Values)文件都是非常常用的文件格式. 正如它英文直译过来的意思一样, 逗号分隔符文件, 每个文件类似于一个表格, 换行意味着表格的一行结束, 而英文逗号用于将每一行分隔为一个个的单元格. 直观理解起来, 你可能会觉得非常简单. 不过在此之前, 不妨先回答以下几个问题, 如果都能知道答案, 那确实是已经非常熟悉这个文件格式了.
[1] csv 文件能否允许每一行的单元格数量不一样?
[2] 单元格之间用逗号分隔, 那如果单元格内容里面本身就有个逗号怎么处理?
[3] 同理, 换行用于开启一行新的数据, 但是如果单元格内容里面本身就有换行符怎么处理?
[4] csv 文件是否应该有表头? 即第一行应该是表头还是数据?
[5] 每行最后一个单元格的数据后面能否有逗号?
要找对于 csv 文件最权威的定义, 就要先介绍一下 RFC(Request For Comments)了. RFC 可以认为是世界上最权威的互联网基本概念介绍大全, 每个概念的介绍都有自己的一个文件编号. 例如 JSON 格式的定义文件 (RFC4627 https://tools.ietf.org/pdf/rfc4627.pdf ),HTTP2.0 的定义文件(RFC7540 https://tools.ietf.org/pdf/rfc7540.pdf ),PNG 图片格式定义文件(RFC2083 https://tools.ietf.org/pdf/rfc2083.pdf ) 等等. 在此不做过多介绍, 想要了解 RFC 的由来可以参考维基百科 https://zh.wikipedia.org/wiki/RFC , 如果感兴趣想了解所有的 RFC 文件, 可参考其官方网站 https://tools.ietf.org/rfc/index .
回到主题, 对于 csv 文件格式, RFC 也有其官方文档描述, 即 RFC4180 https://tools.ietf.org/pdf/rfc4180.pdf . 该文档其实是汇总了各家的 csv 文件实现方式, 并且选取了最大众化的, 被最多人所接受格式并计入此标准中. 该文档其实已经可以回答我们上方的几个问题.
[1] csv 文件能否允许每一行的单元格数量不一样?
不允许, 每一行 (包括表头行) 均拥有相同数量的单元格
[2] 单元格之间用逗号分隔, 如果单元格内本身有逗号怎么处理?
整个单元格可以用双引号包含起来. 也就是说如果单元格内容没有逗号, 那么你可以任何选择是否要用双引号把单元格包含起来. 这就带来另一个问题, 如果单元格内容本身有双引号呢? 你必须使用双引号包含整个单元格, 并且内容中的双引号前面要多加一个双引号做转义. 例如:
- // 正确, hello 与 world 之间有逗号所以必须用引号包含起来, 其他单元格随意
- nihao,qcloud,qq,"weixin"
- hi, taobao,"hello, world", tmall
- // 正确, 注意第二行第三个单元格内容本来是 she said "yes"
- // 但是由于内容本身有双引号, 所以单元格用双引号包含, 且内容中的双引号多写一个做转义
- nihao,qcloud,qq,"weixin"
- hi, taobao,"she said""yes""", tmall
- // 错误, 没有用双引号包含的单元格不准出现双引号
- nihao,qcloud,qq,"weixin"
- hi,taobao,1'10" so fast,tmall
[3] 单元格内容本身有换行符怎么处理?
同上, 单元格用引号括起来. 举例
- // 正确, 下面内容虽然占三行, 但是只认为有两行 csv 数据,"weixin \n a life style" 算一个单元格
- nihao,qcloud,qq,"weixin
- a life style"hi,taobao,1'10" so fast,tmall
[4] csv 文件是否应该有表头?
随意, 可以有也可以没有. 只不过表头行的单元格数量要保持和数据一致
[5] 每行的最后一个单元格后面能否有逗号?
不可以, 以逗号结尾的行被认为最后有个空的单元格.
另外需要注意的是, csv 和 http 协议一样, 换行符是 "\r\n"(即 CRLF), 只不过大部分 csv 相关的库做了兼容, 可以兼容以 "\r" 或者 "\n" 结尾的情况. 当你自己要用程序写入内容到 csv 文件, 或者要写一个读取 csv 文件的程序时, 这些规则就显得尤为重要了. 各个开发语言也都针对 csv 文件的写入和读取有专门的开源库. 切不可在小问题上栽了大坑.
来源: https://www.qcloud.com/developer/article/1168780