TCL多捕获组,用于使用regexp简化csv字符串解析

TCL多捕获组,用于使用regexp简化csv字符串解析,regex,tcl,Regex,Tcl,我试图用TCL regexp解析一个简化的CSV格式。我选择regexp而不是split来执行基本的格式符合性测试 我的问题是,我想使用计数量词,但想从匹配中排除“,” 我的测试线: set line "2017/08/21 16:06:20.0, REALTIME, late by 0.3, EOS450D, 1/640, F/8.0, ISO 100, Partial 450D 0.0%" 到目前为止,我已经: regexp -all {(?:([^\,]*)\,){8}} $l

我试图用TCL regexp解析一个简化的CSV格式。我选择regexp而不是split来执行基本的格式符合性测试

我的问题是,我想使用计数量词,但想从匹配中排除“,”

我的测试线:

set line "2017/08/21 16:06:20.0, REALTIME, late by  0.3, EOS450D,   1/640, F/8.0, ISO   100, Partial 450D 0.0%"
到目前为止,我已经:

regexp -all {(?:([^\,]*)\,){8}} $line dummy date tm off cam exp fnum iso com
我的思考过程是: 获取所有非逗号字符的匹配组,直到下一个逗号。 现在我想匹配这8次,所以我把它放入一个非捕获组,后面跟着一个计数量词。但这与目的背道而驰,因为现在没有任何东西能与之匹配。我需要的是一种方法,使匹配通过CSV 8次,并捕获文本,但不是逗号

我的CSV简化如下。 CSV中没有带引号的字符串 CSV中没有空条目

我已经检查了谷歌的csv匹配,但是由于允许csv内容中出现特殊情况,大多数点击都被放大了

谢谢,
Gert

regexp
命令中,
-all
开关和匹配变量之间的交互作用是,在最后一次匹配迭代中捕获的值用于填充变量。这意味着您不能通过一个捕获组和八次迭代匹配来填充八个变量

您的正则表达式无论如何都不匹配,因为它在最后一个字段后需要逗号

对于这个特定的示例,您可以使用调用

% regexp -all -inline {[^,]+} $line
{2017/08/21 16:06:20.0} { REALTIME} { late by  0.3} { EOS450D} {   1/640} { F/8.0} { ISO   100} { Partial 450D 0.0%}
这意味着匹配所有非逗号的字符组(请注意,逗号并不特殊:不需要转义),并将其作为列表返回

正如您所指出的,这与使用

% split $line ,
(速度也快了五倍左右)

您不想使用
split
,因为您想做一些验证:不清楚您想做什么形式的验证,但您可以轻松验证找到的字段数:

% set fields [split $line ,]
% if {[llength $fields] ne 8} {puts stderr "wrong number of fields"}
您可以将字段存储在变量中并单独验证它们,这比在提取字段时同时验证所有字段要容易得多:

lassign $fields date tm off cam exp fnum iso com
if {![regexp {ISO\s+\d+} $iso]} {puts stderr "in search of valid ISO"}
最好的方法仍然是使用
csv
包拆分数据字符串。即使您现在只想使用这个简化的CSV,也要比您想象的更快,比如说,允许在字段中使用逗号

package require csv
set fields [::csv::split $line]
文件: , , , , , , , , ,

ETA:去掉前导/尾随空格。这有点不寻常,因为CSV数据通常被安排为由分隔符分隔的严格有效文本字段。如果有任何内容需要修剪,通常在保存数据时进行

一个好方法是通过
lmap
/
字符串修剪过滤器将匹配的组放入:

lmap field [regexp -all -inline {[^,]+} $line] {string trim $field}
另一种方法是先去掉逗号周围的空格,然后拆分:

split [regsub -all {\s*,\s*} $line ,] ,
您可以使用通过正则表达式拆分的
split
的Tcllib变量:

package require textutil
::textutil::splitx $line {\s*,\s*}

您还可以将前面的正则表达式替换为
[^\s,][^,]*[^\s,]
(将不匹配少于两个字符的字段)。这是一个即将变得过于复杂而无法使用的正则表达式。

regexp
命令中,
-all
开关和匹配变量之间的交互作用是,在最后一次匹配迭代中捕获的值用于填充变量。这意味着您不能通过一个捕获组和八次迭代匹配来填充八个变量

您的正则表达式无论如何都不匹配,因为它在最后一个字段后需要逗号

对于这个特定的示例,您可以使用调用

% regexp -all -inline {[^,]+} $line
{2017/08/21 16:06:20.0} { REALTIME} { late by  0.3} { EOS450D} {   1/640} { F/8.0} { ISO   100} { Partial 450D 0.0%}
这意味着匹配所有非逗号的字符组(请注意,逗号并不特殊:不需要转义),并将其作为列表返回

正如您所指出的,这与使用

% split $line ,
(速度也快了五倍左右)

您不想使用
split
,因为您想做一些验证:不清楚您想做什么形式的验证,但您可以轻松验证找到的字段数:

% set fields [split $line ,]
% if {[llength $fields] ne 8} {puts stderr "wrong number of fields"}
您可以将字段存储在变量中并单独验证它们,这比在提取字段时同时验证所有字段要容易得多:

lassign $fields date tm off cam exp fnum iso com
if {![regexp {ISO\s+\d+} $iso]} {puts stderr "in search of valid ISO"}
最好的方法仍然是使用
csv
包拆分数据字符串。即使您现在只想使用这个简化的CSV,也要比您想象的更快,比如说,允许在字段中使用逗号

package require csv
set fields [::csv::split $line]
文件: , , , , , , , , ,

ETA:去掉前导/尾随空格。这有点不寻常,因为CSV数据通常被安排为由分隔符分隔的严格有效文本字段。如果有任何内容需要修剪,通常在保存数据时进行

一个好方法是通过
lmap
/
字符串修剪过滤器将匹配的组放入:

lmap field [regexp -all -inline {[^,]+} $line] {string trim $field}
另一种方法是先去掉逗号周围的空格,然后拆分:

split [regsub -all {\s*,\s*} $line ,] ,
您可以使用通过正则表达式拆分的
split
的Tcllib变量:

package require textutil
::textutil::splitx $line {\s*,\s*}

您还可以将前面的正则表达式替换为
[^\s,][^,]*[^\s,]
(将不匹配少于两个字符的字段)。这是一个即将变得过于复杂而无法使用的正则表达式。

我现在无法测试,但从代码看,它可能会阻塞,因为最后一个字段后面没有逗号(这是正确的)。我强烈建议使用Tcllib中的csv包来处理甚至简化的csv。我现在无法测试,但从代码看,它可能会阻塞,因为最后一个字段后面没有逗号(这是正确的)。我强烈建议使用Tcllib中的csv包来处理简化的csv。我喜欢建议的正则表达式。作为正则表达式的一部分,是否有办法修剪前导/尾随空格?(是的,以后可以修剪,但不会