正则表达式拆分 CSV
Posted
技术标签:
【中文标题】正则表达式拆分 CSV【英文标题】:Regex to split a CSV 【发布时间】:2013-08-11 06:03:26 【问题描述】:我知道这个(或类似的)已经被问过很多次了,但是在尝试了很多可能性之后,我一直无法找到一个 100% 有效的正则表达式。
我有一个 CSV 文件,我试图将其拆分为一个数组,但遇到了两个问题:带引号的逗号和空元素。
CSV 看起来像:
123,2.99,AMO024,Title,"Description, more info",,123987564
我尝试使用的正则表达式是:
thisLine.split(/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/)
唯一的问题是在我的输出数组中,第 5 个元素是 123987564 而不是空字符串。
【问题讨论】:
好像是JS。使用 proper parser 怎么样?另外请指定您使用的语言,这样可以避免大量猜测工作。 除了好奇之外,是什么让您想到使用正则表达式? 它的经典 asp(使用 jscript),我认为在处理数据之前处理数据的正则表达式将是提取数据的最简单方法。 “我找不到一个 100% 有效的正则表达式” 这主要是因为这是 CSV 解析器的工作,你应该使用它。例如,试试这个。 github.com/gkindel/CSV-JS 正则表达式不太适合解决这类问题,一个简单的解析器会更好。当然,使用现有的解析器比编写自己的解析器更容易,请参阅 Tomalak 的回答。 【参考方案1】:说明
我认为与其使用拆分,不如简单地执行匹配并处理所有找到的匹配项。
这个表达式将:
用逗号分隔示例文本 将处理空值 将忽略双引号逗号,前提是双引号不嵌套 从返回值中删除分隔逗号 从返回值中修剪周围的引号 如果字符串以逗号开头,则第一个捕获组将返回空值正则表达式:(?:^|,)(?=[^"]|(")?)"?((?(1)[^"]*|[^,"]*))"?(?=,|$)
示例
示例文本
123,2.99,AMO024,Title,"Description, more info",,123987564
使用非 java 表达式的 ASP 示例
Set regEx = New RegExp
regEx.Global = True
regEx.IgnoreCase = True
regEx.MultiLine = True
sourcestring = "your source string"
regEx.Pattern = "(?:^|,)(?=[^""]|("")?)""?((?(1)[^""]*|[^,""]*))""?(?=,|$)"
Set Matches = regEx.Execute(sourcestring)
For z = 0 to Matches.Count-1
results = results & "Matches(" & z & ") = " & chr(34) & Server.htmlEncode(Matches(z)) & chr(34) & chr(13)
For zz = 0 to Matches(z).SubMatches.Count-1
results = results & "Matches(" & z & ").SubMatches(" & zz & ") = " & chr(34) & Server.HTMLEncode(Matches(z).SubMatches(zz)) & chr(34) & chr(13)
next
results=Left(results,Len(results)-1) & chr(13)
next
Response.Write "<pre>" & results
使用非 java 表达式匹配
第 0 组获取包含逗号的整个子字符串 第 1 组获得报价(如果已使用) 第 2 组获取不包括逗号的值
[0][0] = 123
[0][1] =
[0][2] = 123
[1][0] = ,2.99
[1][1] =
[1][2] = 2.99
[2][0] = ,AMO024
[2][1] =
[2][2] = AMO024
[3][0] = ,Title
[3][1] =
[3][2] = Title
[4][0] = ,"Description, more info"
[4][1] = "
[4][2] = Description, more info
[5][0] = ,
[5][1] =
[5][2] =
[6][0] = ,123987564
[6][1] =
[6][2] = 123987564
已编辑
正如 Boris 指出的那样,CSV 格式会将双引号 "
转义为双引号 ""
。尽管 OP 未包含此要求,但如果您的文本包含双双引号,那么您将需要使用此修改后的表达式:
正则表达式:(?:^|,)(?=[^"]|(")?)"?((?(1)(?:[^"]|"")*|[^,"]*))"?(?=,|$)
另请参阅:https://regex101.com/r/y8Ayag/1
还应该指出,Regex 是一种模式匹配工具,而不是解析引擎。因此,如果您的文本包含双双引号,则在模式匹配完成后它仍将包含双双引号。使用此解决方案,您仍然需要搜索双双引号并在捕获的文本中替换它们。
【讨论】:
请问您使用什么软件/网站来生成这些图表? 更正这不会尊重转义的引号,但这在技术上超出了范围。 @RoYoMi,你的正则表达式很完美!你能适应 POSIX 正则表达式吗?所以我们可以在PostgreSQL这样的数据库中使用,函数regexp_matches(string, regex)产生数组作为返回,但是使用this limited regex syntax。 @ReiMiyasaka,您只是部分正确,这不适用于嵌套引号,但是嵌套引号不是原始问题的一部分。在我的第三个要点providing double quotes are not nested
中已经披露了这不适用于嵌套引号的事实。
我已经稍微调整了您的公式,使其与我的一切完美匹配。 (?:^|,)(?=[^"]|(")?)"?((?(1).*?(?=",)|[^,]*))(?=|$ ) -> 部分 .*?(?=",) 而不是 [^"]* 匹配除即将到来的 " 之外的所有字符,在第一组已积极匹配之后。如果第一组匹配,则字符串确实以引号开头并且应该也结束了。GL,我希望这对其他人也有帮助。【参考方案2】:
对此进行了一段时间的研究并提出了这个解决方案:
(?:,|\n|^)("(?:(?:"")*[^"]*)*"|[^",\n]*|(?:\n|$))
Try it out here!
此解决方案处理“不错”的 CSV 数据,例如
"a","b",c,"d",e,f,,"g"
0: "a"
1: "b"
2: c
3: "d"
4: e
5: f
6:
7: "g"
还有更丑的东西
"""test"" one",test' two,"""test"" 'three'","""test 'four'"""
0: """test"" one"
1: test' two
2: """test"" 'three'"
3: """test 'four'"""
这是explanation of how it works:
(?:,|\n|^) # all values must start at the beginning of the file,
# the end of the previous line, or at a comma
( # single capture group for ease of use; CSV can be either...
" # ...(A) a double quoted string, beginning with a double quote (")
(?: # character, containing any number (0+) of
(?:"")* # escaped double quotes (""), or
[^"]* # non-double quote characters
)* # in any order and any number of times
" # and ending with a double quote character
| # ...or (B) a non-quoted value
[^",\n]* # containing any number of characters which are not
# double quotes ("), commas (,), or newlines (\n)
| # ...or (C) a single newline or end-of-file character,
# used to capture empty values at the end of
(?:\n|$) # the file or at the ends of lines
)
【讨论】:
很好的解决方案!也适用于 .NET 的 Regex 类。 @HermanCordes,很高兴它有帮助!我刚刚删除了一些不必要的非捕获组,所以现在应该快一点。 (要点已更新,但这篇文章没有……哎呀!) 欣赏这个解决方案。 PSA 如果您有一个空的第一列(例如,foo,bar
),它将不会被捕获。一种解决方法是在解析之前将空引号 ""
添加到此类行。
完美。即使使用简单的示例,所有其他答案也会产生不正确的结果,但这个答案适用于我的所有案例(不一致的外壳 + 值内的逗号)。
还是很有价值的回答,谢谢!这适用于值中的换行符和(最重要的是)双引号 ("") 转义的引号。【参考方案3】:
几个月前我为一个项目创建了这个。
".+?"|[^"]+?(?=,)|(?<=,)[^"]+
它在 C# 中工作,当我选择 Python 和 PCRE 时,Debuggex 很高兴。 javascript 无法识别这种形式的 Proceeded By ?。
对于您的值,它将创建匹配项
123
,2.99
,AMO024
,Title
"Description, more info"
,
,123987564
请注意,引号中的任何内容都没有前导逗号,但空值用例需要尝试与前导逗号匹配。完成后,根据需要修剪值。
我使用RegexHero.Net 来测试我的正则表达式。
【讨论】:
虽然问题中的示例没有提及,但完美的正则表达式算法还需要处理字段内的引号字符,例如:single,"quoted","with ""quotes""",end
。你的还没有。
我也想知道第一个“?”过去的“+” - 这对我来说看起来是多余的。而且我必须将其更改为"[^"]+"|[^"]+?(?=,)|(?<=,)[^"]+
,否则它无法使用我的正则表达式版本(在 Real Studio 2012 中)正确扫描引用的字段。
“?”在“+”分配非贪婪状态后,它将尽可能多地抓取到下一个字符的第一个实例。例如,如果原始帖子有两个引用值,则不使用问号可能会获取引号的第一个实例和最后一个实例之间的所有文本。双引号很难,我看看能不能解决。
如果双引号在内部,可以使用公式(?:")
轻松扫描它们,但不幸的是,这在带引号的字段末尾不起作用。查看链接的 Q,我发现通过将引号加倍来转义引号也不是通用的——Apple 的 Numbers 可以做到这一点,但其他应用程序可能会用 \"
转义。另外:开头或结尾的空字段不起作用。一开始我在代码中使用了一个特殊情况,最后我在正则表达式中添加了|,$
。
,?".+?"|[^"]+?(?=,)|[^"]+ 只是一个小修改,现在效果很好!【参考方案4】:
我迟到了,但以下是我使用的正则表达式:
(?:,"|^")(""|[\w\W]*?)(?=",|"$)|(?:,(?!")|^(?!"))([^,]*?)(?=$|,)|(\r\n|\n)
此模式具有三个捕获组:
-
引用单元格的内容
未引用单元格的内容
新行
此模式处理以下所有内容:
没有任何特殊功能的正常单元格内容:一、二、三 包含双引号的单元格(" 转义为 ""):没有引号,"a ""quoted"" thing",end 单元格包含换行符:一、二\n三、四 具有内部引号的普通单元格内容:one,two"three,four 单元格包含引号,后跟逗号:one,"two ""three"",four",fiveSee this pattern in use.
如果您使用的是功能更强大的正则表达式,带有命名组和后视功能,我更喜欢以下方法:
(?<quoted>(?<=,"|^")(?:""|[\w\W]*?)*(?=",|"$))|(?<normal>(?<=,(?!")|^(?!"))[^,]*?(?=(?<!")$|(?<!"),))|(?<eol>\r\n|\n)
See this pattern in use.
编辑
(?:^"|,")(""|[\w\W]*?)(?=",|"$)|(?:^(?!")|,(?!"))([^,]*?)(?=$|,)|(\r\n|\n)
只要您不使用 Javascript,这种稍微修改的模式就可以处理第一列为空的行。出于某种原因,Javascript 将使用此模式省略第二列。我无法正确处理这种极端情况。
【讨论】:
在所有已发布的解决方案中,这个最适合我。它经得起各种边缘情况。但是,它不处理以逗号开头的字符串。例如,",second,third" 应该产生 3 个匹配项,但只产生 2 个。 @bubleboy - 我喜欢你的正则表达式,但它似乎无法处理 csv 的最后一列为空的情况..., column5,
有没有办法调整正则表达式以捕获那些尾随列?
@RHarris - 谢谢。我测试了这个模式,它的最后一列是空的。您使用什么语言?并非所有的正则表达式实现都是相同的。语言可能有不同的要求。
@RHarris - 我在 C# 中尝试过,它确实按预期工作:var pat = new System.Text.RegularExpressions.Regex(@"(?:^""|,"")(""""|[\w\W]*?)(?="",|""$)|(?:^(?!"")|,(?!""))([^,]*?)(?=$|,)|(\r\n|\n)", System.Text.RegularExpressions.RegexOptions.Multiline);
var all = pat.Matches(",one,two,\"lets test, some \"\"quotes\"\"\",three,");
这导致MatchCollection(5) [], [,two], [,"lets test, some ""quotes""], [,three], [,]
抱歉,我意识到是我的 TextReader.ReadLine() 导致了这个问题。倒数第二列有\r\n
。 (例如 `...",three\r\n and some more text,". 我从来没有真正得到最后一列。感谢您的帮助。【参考方案5】:
我也需要这个答案,但我发现这些答案虽然内容丰富,但对于其他语言有点难以理解和复制。这是我为 CSV 行中的单个列想出的最简单的表达式。我不分裂。我正在构建一个正则表达式来匹配 CSV 中的一列,所以我没有拆分行:
("([^"]*)"|[^,]*)(,|$)
这匹配 CSV 行中的单个列。表达式的第一部分"([^"]*)"
是匹配引用条目,第二部分[^,]*
是匹配非引用条目。然后是,
或行尾$
。
以及附带的调试表达式来测试表达式。
https://www.debuggex.com/r/s4z_Qi2gZiyzpAhx
【讨论】:
它在 JavaScript 中工作(这不是 OP 所要求的,但知道它会有所帮助)。 如果你使用这个,你应该确保该行不以 \r 或 \n(或 \r\n)结尾。 不处理转义的双引号 ("") 它确实处理双引号 ("") 和转义 (\") 引号,因为此正则表达式返回一个完整字段,无论是带引号的还是不带引号的,用逗号分隔。双引号和获取字段后仍然需要转义引号,但是通过这个简洁的正则表达式正确地完成了获取完整字段的主要任务。如果您在编程代码中使用这个正则表达式,您可以轻松地对双精度或之后转义执行替换操作(但前提是找到的字符串以引号开头。【参考方案6】:我个人尝试了许多 RegEx 表达式,但没有找到适合所有情况的完美表达式。
我认为正则表达式很难正确配置以正确匹配所有情况。 虽然很少有人不喜欢命名空间(我也是其中的一员),但我提出了一些属于 .Net 框架的内容,并在所有情况下始终给我正确的结果(主要是很好地管理每个双引号情况):
Microsoft.VisualBasic.FileIO.TextFieldParser
在这里找到它:***
使用示例:
TextReader textReader = new StringReader(simBaseCaseScenario.GetSimStudy().Study.FilesToDeleteWhenComplete);
Microsoft.VisualBasic.FileIO.TextFieldParser textFieldParser = new TextFieldParser(textReader);
textFieldParser.SetDelimiters(new string[] ";" );
string[] fields = textFieldParser.ReadFields();
foreach (string path in fields)
...
希望对您有所帮助。
【讨论】:
【参考方案7】:在 Java 中,这种模式 ",(?=([^\"]*\"[^\"]*\")*(?![^\"]*\"))"
几乎对我有用:
String text = "\",\",\",,\",,\",asdasd a,sd s,ds ds,dasda,sds,ds,\"";
String regex = ",(?=([^\"]*\"[^\"]*\")*(?![^\"]*\"))";
Pattern p = Pattern.compile(regex);
String[] split = p.split(text);
for(String s:split)
System.out.println(s);
输出:
","
",a,,"
",asdasd a,sd s,ds ds,dasda,sds,ds,"
缺点:当列有奇数个引号时不起作用:(
【讨论】:
【参考方案8】:将 JScript 用于经典 ASP 页面的优势在于,您可以使用为 JavaScript 编写的众多库之一。
喜欢这个:https://github.com/gkindel/CSV-JS。下载它,将它包含在您的 ASP 页面中,并用它解析 CSV。
<%@ language="javascript" %>
<script language="javascript" runat="server" src="scripts/csv.js"></script>
<script language="javascript" runat="server">
var text = '123,2.99,AMO024,Title,"Description, more info",,123987564',
rows = CSV.parse(line);
Response.Write(rows[0][4]);
</script>
【讨论】:
不幸的是,我需要在我的 ASP 脚本中完成解析 但是上面的是 ASP。你有没有读过我回答中的文字?【参考方案9】:Aaaand 另一个答案在这里。 :) 因为我无法让其他人相当工作。
我的解决方案都处理转义引号(两次出现),并且在匹配中不包含分隔符。
请注意,我一直在匹配 '
而不是 "
,因为那是我的场景,但只需在模式中替换它们即可获得相同的效果。
到这里(如果您使用下面的注释版本,请记住使用“忽略空格”标志/x
):
# Only include if previous char was start of string or delimiter
(?<=^|,)
(?:
# 1st option: empty quoted string (,'',)
'2
|
# 2nd option: nothing (,,)
(?:)
|
# 3rd option: all but quoted strings (,123,)
# (included linebreaks to allow multiline matching)
[^,'\r\n]+
|
# 4th option: quoted strings (,'123''321',)
# start pling
'
(?:
# double quote
'2
|
# or anything but quotes
[^']+
# at least one occurance - greedy
)+
# end pling
'
)
# Only include if next char is delimiter or end of string
(?=,|$)
单行版本:
(?<=^|,)(?:'2|(?:)|[^,'\r\n]+|'(?:'2|[^']+)+')(?=,|$)
Debuggex Demo
regex101 example
【讨论】:
【参考方案10】:另一个答案具有一些额外的功能,例如支持包含转义引号和 CR/LF 字符(跨多行的单个值)的引用值。
注意:虽然以下解决方案可能适用于其他正则表达式引擎,但按原样使用它需要您的正则表达式引擎将 multiple named capture groups using the same name 视为一个单一的捕获组。 (.NET 默认会这样做)
当 CSV 文件/流的多行/记录(匹配 RFC standard 4180)被传递到下面的正则表达式时,它将为每个非空行/记录返回一个匹配项。每个匹配项都将包含一个名为 Value
的捕获组,其中包含该行/记录中的捕获值(如果行/记录末尾有一个开放引号,则可能是一个 OpenValue
捕获组) em>。
这是注释模式(测试它on Regexstorm.net):
(?<=\r|\n|^)(?!\r|\n|$) // Records start at the beginning of line (line must not be empty)
(?: // Group for each value and a following comma or end of line (EOL) - required for quantifier (+?)
(?: // Group for matching one of the value formats before a comma or EOL
"(?<Value>(?:[^"]|"")*)"| // Quoted value -or-
(?<Value>(?!")[^,\r\n]+)| // Unquoted value -or-
"(?<OpenValue>(?:[^"]|"")*)(?=\r|\n|$)| // Open ended quoted value -or-
(?<Value>) // Empty value before comma (before EOL is excluded by "+?" quantifier later)
)
(?:,|(?=\r|\n|$)) // The value format matched must be followed by a comma or EOL
)+? // Quantifier to match one or more values (non-greedy/as few as possible to prevent infinite empty values)
(?:(?<=,)(?<Value>))? // If the group of values above ended in a comma then add an empty value to the group of matched values
(?:\r\n|\r|\n|$) // Records end at EOL
这是没有所有 cmets 或空格的原始模式。
(?<=\r|\n|^)(?!\r|\n|$)(?:(?:"(?<Value>(?:[^"]|"")*)"|(?<Value>(?!")[^,\r\n]+)|"(?<OpenValue>(?:[^"]|"")*)(?=\r|\n|$)|(?<Value>))(?:,|(?=\r|\n|$)))+?(?:(?<=,)(?<Value>))?(?:\r\n|\r|\n|$)
[这是来自 Debuggex.com 的可视化][3](捕获组以清晰命名):
![Debuggex.com 可视化][4]
可以在我对类似问题here、C# pad here 或here 的回答中找到有关如何使用正则表达式模式的示例。
【讨论】:
如果你有这样的字符串 ""a",",b",c,"d,D",e,f,,"g"",ReGexp 确实会检测到一些值 你是对的。正则表达式确实检测值。你说不应该?如果我在答案中使用regexstorm.net 链接来测试没有外引号的字符串(“a”,“b”...“g”),我会在“表格”选项卡中得到 8 个匹配项,正如我所期望的那样它们是: [a] [,b] [c] [d,D] [e] [f] [] [g] 如果包含外部引号,则它是无效的 csv,因为引号没有正确转义。跨度> 【参考方案11】:如果你知道你不会有一个空字段 (,,) 那么这个表达式很有效:
("[^"]*"|[^,]+)
如下例...
Set rx = new RegExp
rx.Pattern = "(""[^""]*""|[^,]+)"
rx.Global = True
Set col = rx.Execute(sText)
For n = 0 to col.Count - 1
if n > 0 Then s = s & vbCrLf
s = s & col(n)
Next
但是,如果您预计会有一个空字段并且您的文本相对较小,那么您可能会考虑在解析之前用空格替换空字段以确保它们被捕获。比如……
...
Set col = rx.Execute(Replace(sText, ",,", ", ,"))
...
如果您需要保持字段的完整性,您可以恢复逗号并测试循环内的空格。这可能不是最有效的方法,但它可以完成工作。
【讨论】:
这是解决我问题的那个。我不支持空字段,这样我就可以抓取所有内容,而如果我之前在任何字段中有引号,则会导致并发症。【参考方案12】:我正在使用这个,它适用于逗号分隔符和双引号转义。 通常这应该可以解决您的问题:
/(?<=^|,)(\"(?:[^"]+|"")*\"|[^,]*)(?:$|,)/g
【讨论】:
【参考方案13】:我使用这种表达方式。它考虑了我遇到的逗号后的空格。
(?:,"|^"|, ")(""|[\w\W]*?)(?=",|"$)|(?:,(?!")|^(?!"))([^,]*?)(?=$|,)|(\r\n|\n)
【讨论】:
【参考方案14】:我也需要从 SQL 插入语句中拆分 CSV 值。
在我的例子中,我可以假设字符串是用单引号括起来的,而数字不是。
csv.split(/,((?=')|(?=\d))/g).filter(function(x) return x !== '';);
由于某些可能显而易见的原因,此正则表达式会产生一些空白结果。我可以忽略这些,因为我的数据中的任何空值都表示为 ...,'',...
而不是 ...,,...
。
【讨论】:
【参考方案15】:如果我使用“g”标志尝试@chubbsondubs 在http://regex101.com 上发布的正则表达式,则匹配项仅包含“,”或空字符串。
使用这个正则表达式:(?:"([^"]*)"|([^,]*))(?:[,])
我可以匹配 CSV 的部分(包括引用的部分)。 (该行必须以“,”结尾,否则无法识别最后一部分。)https://regex101.com/r/dF9kQ8/4
如果 CSV 看起来像:"",huhu,"hel lo",world,
有 4 场比赛:
''
'呼呼'
'你好'
'世界'
【讨论】:
【参考方案16】:,?\s*'.+?'|,?\s*".+?"|[^"']+?(?=,)|[^"']+
此正则表达式适用于单引号和双引号,也适用于另一个引号!
【讨论】:
【参考方案17】:这个匹配我在 c# 中需要的所有内容:
(?<=(^|,)(?<quote>"?))([^"]|(""))*?(?=\<quote>(?=,|$))
去掉引号
让新行
允许在带引号的字符串中使用双引号
允许在引号字符串中使用逗号
【讨论】:
【参考方案18】:匹配带有转义[双引号]单引号的单引号值的正确正则表达式是:
'([^n']|(''))+'
【讨论】:
以上是关于正则表达式拆分 CSV的主要内容,如果未能解决你的问题,请参考以下文章