grep命令用于匹配按字母顺序排列的行。

5
我需要帮助来解决一个使用grep的正则表达式问题,它将搜索一个文件并显示将[a-z]按字母顺序排列在每个“集合”中的行(通过示例将变得清晰)。
有效匹配示例:
a96d7e75-4432-41de-835c-625a636c1914 prefranks
b028224d-314b-4b03-a873-1436838f9233 escape

无效匹配示例:
c69f34e8-905e-4ce8-7893-a3e271d6f48c reconvince
e9db0700-f72b-4bea-ae37-e18ec6ca80d3 lumberjacks

我使用了:

egrep '^([^a-f-]*a?[^a-f-]*b?[^a-f-]*c?[^a-f-]*d?[^a-f-]*e?[^a-f-]*f?-){4}[^a-f-]*a?[^a-f-]*b?[^a-f-]*c?[^a-f-]*d?[^a-f-]*e?[^a-f-]*f?[0-9]* ' text.txt

它没有给出任何无效的匹配,但却忽略了像下面这样的有效示例,我无法弄清楚为什么。
6cd11113-bcf3-4f73-85f5-145b49225244 neoconservative
96239f18-5c62-495a-b1f8-50759443b885 fellest
51771125-b4d8-4cf8-a3d9-67117263f708 macular
a266f798-d772-47f0-9bdf-451939c2007e buntings

@CarySwoveland 对于这个冗长的问题,我表示歉意。我原以为我的命令只需要做几个改变,但正则表达式中的米并不是我预期的。这是一个小任务,要求答案简短,并且只需使用简单的grep命令完成。 - undefined
egrep已经在近20年中被弃用,取而代之的是grep -E - undefined
我需要向你道歉。起初我说一个正则表达式必须非常长且非常复杂。事实上,所需的正则表达式相当容易处理。 - undefined
@EdMorton 然而,tar 也已被pax所取代,你知道那是怎么回事。 - undefined
在字符串a96d7e75-4432-41de-835c-625a636c1914 prefranks中,你认为有效的部分,你在一个"e"后面有一个"d",在一个"e"后面有一个"c",在一个"r"后面有一个"e",在一个"r"后面有一个"a",在一个"n"后面有一个"k" :-) - undefined
@Dominique,问题不太清楚,但我认为楼主想要的是字母按顺序排列,就在每个以-分隔的部分内,比如a96d7e75,而不是整个第一个字段或整行。 - undefined
4个回答

5
你的egrep在带有f之后的数字子字符串上失败了。
当你将f?*-){4}替换为f?[^a-f]*-){4}时,它将起作用。
egrep '^([^a-f-]*a?[^a-f-]*b?[^a-f-]*c?[^a-f-]*d?[^a-f-]*e?[^a-f-]*f?[^a-f]*-){4}[^a-f-]*a?[^a-f-]*b?[^a-f-]*c?[^a-f-]*d?[^a-f-]*e?[^a-f-]*f?[0-9]* ' text.txt

当你使用变量时,阅读起来可能同样困难,但会更加简短。
x='[^a-f-]'
egrep "^($x*a?$x*b?$x*c?$x*d?$x*e?$x*f?$x*-){4}$x*a?$x*b?$x*c?$x*d?$x*e?$x*f?[0-9]* " text.txt

你可以做一个小循环。
#!/bin/bash
while IFS= read -r line; do
  charline="${line//[0-9]/}"
  if [[ "$charline" =~ ^(a?b?c?d?e?f?-){4}(a?b?c?d?e?f?)\ .* ]]; then
    echo "${line}"
  fi
done < text.txt

最后的解决方案可以变得更小(并且更难阅读):
#!/bin/bash
while IFS= read -r line; do
  [[ "${line//[0-9]/}" =~ ^(a?b?c?d?e?f?-){4}(a?b?c?d?e?f?)\ .* ]] &&
    echo "${line}"
done < text.txt

编辑:上述解决方案不接受令牌中的双字母。
如果您想接受这些,请将解决方案更改为:
#/bin/bash
while IFS= read -r line; do
  [[ "${line//[0-9]/}" =~ ^(a*b*c*d*e*f*-){4}(a*b*c*d*e*f*)\ .* ]] &&
    echo "${line}"
done < text.txt

编辑2:
当你想要接受双字母,并且知道输入始终如给定的示例(仅限令牌[0-9a-f],令牌之间的-以及最后一个令牌后面的空格),你可以使用以下方法。
grep -E '^([a0-9]*[b0-9]*[c0-9]*[d0-9]*[e0-9]*[f0-9]*[- ]){5}' text.txt

2
如果您有多个相同的字母,比如aa96d7e75-4432...,这个就会出错。 - undefined
@dawg 描述要求按字母顺序排列,但 OP 的代码也是使用唯一的字母。OP 问为什么他的代码没有显示某些行,所以我在他的代码中寻找改进。我添加了一个支持重复字母的解决方案。 - undefined

4
你似乎想要在第一个空格之前验证数据的第一部分。这些字符串由字符a-f、0-9和连字符组成。
如果你可以使用grep -P或者在Mac上使用ggrep -P来进行Perl兼容的正则表达式,你可以使用负向先行断言来确保在第一部分中,在匹配到f之后没有在[a-e]范围内的字符,在匹配到e之后没有在[a-d]范围内的字符,以此类推。
^(?![a-f\d-]*(?:f\d*[a-e]|e\d*[a-d]|d\d*[abc]|c\d*[ab]|b\d*a))[a-f\d]+(?:-[a-f\d]+){4} 
  • ^ 字符串的开头
  • (?! 负向前瞻,断言右侧不是
    • [a-f\d-]* 匹配可选的字符 a-f,一个数字或者 -
    • (?: 非捕获组,用于多个可选项
      • f\d*[a-e] 匹配一个 f,可选的数字,然后是一个在范围 [a-e] 内的字符
      • | 或者
      • e\d*[a-d] 匹配一个 e,可选的数字,然后是一个在范围 [a-d] 内的字符
      • | 或者
      • d\d*[abc] 同样适用于 d
      • | 或者
      • c\d*[ab] 同样适用于 c
      • | 或者
      • b\d*a 同样适用于 b
    • ) 关闭该组
  • ) 关闭前瞻
  • [a-f\d]+ 匹配 1 个或多个字符 a-f 或者数字
  • (?:-[a-f\d]+){4} 重复 4 次匹配 - 和 1 个或多个字符 a-f 或者数字,后面跟一个空格
使用grep -P的示例
grep -P '^(?![a-f\d-]*(?:f\d*[a-e]|e\d*[a-d]|d\d*[abc]|c\d*[ab]|b\d*a))[a-f\d]+(?:-[a-f\d]+){4} ' text.txt

输出

a96d7e75-4432-41de-835c-625a636c1914 prefranks
b028224d-314b-4b03-a873-1436838f9233 escape
6cd11113-bcf3-4f73-85f5-145b49225244 neoconservative
96239f18-5c62-495a-b1f8-50759443b885 fellest
51771125-b4d8-4cf8-a3d9-67117263f708 macular
a266f798-d772-47f0-9bdf-451939c2007e buntings

查看一个正则表达式演示

2
眼睛紧张的正则表达式 :-) - undefined
1
@anubhava 这里无可否认 :-) 幸运的是,原帖的作者已经有点习惯了。 - undefined
在我回答的最后一次编辑中,我添加了一个简单的grep,没有使用(否定)lookaheads。 - undefined
1
@WalterA 是的,但这也会匹配 ----- 或者 5 个空格。 - undefined
1
@Thefourthbird 我想要一个简单的解决方案来处理良好的输入。使用 grep -E '^([a0-9]*[b0-9]*[c0-9]*[d0-9]*[e0-9]*[f0-9]*[- ]+){5}' text.txt 可以支持 ----- - undefined

3
用Grep很难... 但是考虑到:
$ cat file
a96d7e75-4432-41de-835c-625a636c1914 prefranks
b028224d-314b-4b03-a873-1436838f9233 escape
c69f34e8-905e-4ce8-7893-a3e271d6f48c reconvince
e9db0700-f72b-4bea-ae37-e18ec6ca80d3 lumberjacks

你可以使用Ruby。
ruby -lane 'puts $_ if $F[0].split(/-/).
                        map{|a| a.scan(/[a-f]/)}.all?{|a| a==a.sort}' file

输出:

a96d7e75-4432-41de-835c-625a636c1914 prefranks
b028224d-314b-4b03-a873-1436838f9233 escape

或者,任何awk:
awk '{
    num_fields=split($1,fi,"-")
    for(f=1; f<=num_fields; f++) {
        gsub(/[^a-f]/,"",fi[f])
        if (length(fi[f])>1) 
            for(i=2; i<=length(fi[f]); i++) 
                if (substr(fi[f],i-1,1)>substr(fi[f],i,1)) next
    }
} 1' file
# same output

一个替代的方法是从"aa96d7e75-44d3a2"中删除数字,得到s = "aade-da",然后将ss.gsub(/[a-z]+/) { |s| s.each_char.sort.join } #=> "aade-ad"进行比较。由于s与排序后的字符串不同,因此该字符串(行)没有匹配项。我不记得你在之前的评论中提到的问题。在这里,我无意中在之前的评论中写成了scan,使用split可能更好。 - undefined

0
我明白OP出于某种原因需要一个grep解决方案,但是将来阅读这个问题的其他人可能没有同样的限制。
牢记这句话
有些人在面对问题时会想:“我知道,我会使用正则表达式。”现在他们有两个问题了。
并且可以使用任何awk:
$ cat tst.awk
{
    str = $1
    gsub(/[0-9]+/,"",str)
    numSegs = split(str,segs,"-")
    for ( segNr=1; segNr<=numSegs; segNr++ ) {
        seg = segs[segNr]
        numChars = length(seg)
        currChar = substr(seg,1,1)
        for ( charNr=2; charNr<=numChars; charNr++ ) {
            prevChar = currChar
            currChar = substr(seg,charNr,1)
            if ( currChar <= prevChar ) {
                next
            }
        }
    }
    print
}

$ awk -f tst.awk file
a96d7e75-4432-41de-835c-625a636c1914 prefranks
b028224d-314b-4b03-a873-1436838f9233 escape
6cd11113-bcf3-4f73-85f5-145b49225244 neoconservative
96239f18-5c62-495a-b1f8-50759443b885 fellest
51771125-b4d8-4cf8-a3d9-67117263f708 macular
a266f798-d772-47f0-9bdf-451939c2007e buntings


gsub(/[0-9-]+/,"",str) 应该改为 gsub(/[0-9]+/,"",str) - undefined
@WalterA没错,现在已经修复了,谢谢。我在从[^a-f-]改变时搞砸了。 - undefined
如果(currChar <= prevChar)在你接受字母出现两次时是可以的(在我看来这是问题的正确解释),并且可以简单地改为(currChar < prevChar)当你想要接受双字母时。这种灵活性展示了awk的强大之处。在我的解决方案中,这个小细节需要完全不同的解决方案。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接