假设我有一个如下所示的文件
1,2,3-5,6
1,2,3-5,6,
1
1-3
1,2,3-,4,5-7
1,2,3-,4,5-7,
1,2,-3,4,5
1,2,-,3,4
1,2,,,3,4
,1,2,3
只有以下规则的组合才应被视为有效:
- 范围
[0-9]+-[0-9]+
- 团体
[0-9]+,[0-9]+
- 单号
[0-9]+
以逗号结尾的行也应被视为有效
我只想提取
1,2,3-5,6
1,2,3-5,6,
1
1-3
由于下面显示的其他行不符合规则
1,2,3-,4,5-7
1,2,3-,4,5-7,
1,2,-3,4,5
1,2,-,3,4
1,2,,,3,4
,1,2,3
因为有些行的范围不完整,有些行的组中缺少数字
PS:仅PCRE
兼容的grep
解决方案会很棒,但也欢迎其他解决方案
答案1
与您列出的字符串(以及以 开头的字符串,
)匹配的完整 PCRE 可能是:
grep -P '^([0-9]+(-[0-9]+)?(,|$))+$'
我们是怎么到达那里的?
匹配的最基本元素是数字,我们假设[0-9]
,或者 PCRE 中更简单的元素\d
,是英语 (ASCII) 数字的正确正则表达式。哪个也可能不是。它可以匹配天城文数字, 例如。那么你需要写:[0123456789]
准确地说。
然后,一个连续数字将匹配为[0-9]+
.
数字(1 或 3 或 26)后可以是破折号“-”,后跟一个或多个数字(又是一个数字):
[0-9]+(-[0-9]+)?
其中?
使短划线数字序列可选。
然后,每个数字:(3
或数字范围4-9
:)后应跟一个逗号,
(多次):
([0-9]+(-[0-9]+)?,)+
除了最后一个逗号可能会丢失:
([0-9]+(-[0-9]+)?(,|$))+
并且,如果需要,可能会出现前导逗号:
(^|,)([0-9]+(-[0-9]+)?(,|$))+
将正则表达式锚定到测试文本的开头和结尾是一个非常好的主意:
^((^|,)([0-9]+(-[0-9]+)?(,|$))+)$
您可以测试和编辑本网站中的 PCRE 正则表达式
如果应拒绝前导逗号,请使用:
^(([0-9]+(-[0-9]+)?(,|$))+)$
这使得正则表达式机器没有任何可选的解释。所有都必须匹配,任何不匹配的都会被拒绝。
它可以写成(GNU)扩展正则表达式:
grep -E '^(([0-9]+(-[0-9]+)?(,|$))+)$'
作为基本正则表达式 (BRE):
grep '^\(\([0-9]\{1,\}\(-[0-9]\{1,\}\)\{0,1\},\{0,1\}\)\{1,\}\)$'
如果逗号,
是可选的{0,1}
,正则表达式引擎可能会做出一些关于匹配内容的决定。
描述性正则表达式?
(?x)
更具描述性的正则表达式,带有空格和注释,可以通过in开头来获得pcregrep
pcregrep '(?x) # tell the regex engine to allow
# white space and comments.
(?(DEFINE) # subroutines that will be used.
(?<nrun> [0-9]+) # run of digits (n-run).
# define a range pair. A number run followed by
# an optional ( dash and another number run )
(?<range> (?&nrun) (-(?&nrun))? ) # range pair.
(?<sep> ,) # separator used.
) # end of definitions.
# Actual regex to use:
# (range) that ends in a (sep)
# or is at the end of the line,
# several times (+).
^( (?&range) ((?&sep)|$) )+$
' file
该正则表达式(一旦编译)与原始正则表达式完全相同,并且运行速度同样快。当然,编译正则表达式需要花费(可以忽略不计的)额外时间。
测试示例是这里
答案2
用于awk
将每行分解为逗号分隔的字段,然后将破折号上的这些字段拆分为子字段,同时丢弃包含不需要的字段或子字段的行:
BEGIN { FS = "," }
{
for (i = 1; i <= NF; ++i) {
# Only the 1st field is allowed to be
# empty, but only if there are further
# fields (avoids empty lines).
if ($i == "" && (i != 1 || NF == 1)) next
# If the field is split on dashes, it
# should split into no more than two
# elements.
if ((n = split($i, a, "-")) > 2) next
# Each split-up element needs to be made
# up of decimal digits only.
for (j = 1; j <= n; ++j)
if (a[j] !~ "^[[:digit:]]+$") next
}
# The current line is ok to print.
print
}
这会像这样使用
awk -f script file
script
程序所在的位置awk
。
或者,作为“一次性”:
awk -F, '{for(i=1;i<=NF;++i){if(($i==""&&(i!=1||NF==1))||((n=split($i,a,"-"))>2))next;for(j=1;j<=n;++j)if(a[j]!~"^[[:digit:]]+$")next}};1' file
5-2
您可以轻松地在循环后添加对“向后范围”(例如)的检查j
:
if (n == 2 && a[1] > a[2]) next
答案3
$ perl -n -e 'print if /^((\d+(-\d+)?)(,|$))+$/g' input.txt
1,2,3-5,6
1,2,3-5,6,
1
1-3
或者,与 GNU grep 相同:
$ grep -P '^((\d+(-\d+)?)(,|$))+$' input.txt
1,2,3-5,6
1,2,3-5,6,
1
1-3
答案4
使用 Raku(以前称为 Perl_6)
raku -ne '.put if m:g/^^ [ [\d+ [\-\d+]?] [\,|$$] ]+ $$/;'
输入示例:
1,2,3-5,6
1,2,3-5,6,
1
1-3
1,2,3-,4,5-7
1,2,3-,4,5-7,
1,2,-3,4,5
1,2,-,3,4
1,2,,,3,4
,1,2,3
示例输出:
1,2,3-5,6
1,2,3-5,6,
1
1-3
使用 Raku 的一个优点是匹配器内的空白容忍度。这使得代码更具可读性。其次,基本正则表达式引擎的修饰符(例如:global
获取前导冒号)并出现在匹配结构的开头m/.../
。这也使得正则表达式更具可读性。
从字面上看上面的正则表达式,它说:'查找一个或多个数字,后跟一个可选的(零或一个)破折号,一个或多个数字,后跟逗号或行尾 ( $$
),整个前面的模式重复一个或-更多次。
现在你可能会问自己,“那又怎样?看起来就像 Perl5 一样。”那是因为上面的代码几乎是 Perl5/PCRE 的直接翻译。事实上,Raku(即 Perl6)有一个新的“分隔符”习惯用法(即“修改量词”),可以用来解决常见的正则表达式问题。以机智:
raku -ne '.put if m:g/^^ [\d+ [\-\d+]? ]+ % "," $$/;'
或者
raku -ne '.put if m:g/^^ [\d+ [\-\d+]? ]+ %% "," $$/;'
第一行使用%
检测匹配,其中逗号分隔符插入到左侧的模式之间。第二行 using%%
执行相同的操作 - 但也允许尾随逗号。你的选择。
Perl 大师 Damian Conway 指出,Raku(即 Perl6)代表了正则表达式的一种全新风格:如果您尝试一下,您可能会同意。
https://docs.raku.org/language/regexes#Modified_quantifier:_%,_%%
https://youtu.be/ubvSjW6Nyqk
https://raku.org/