提取与“ sed”匹配的正则表达式,而不打印周围的字符


24

对于所有在那里的“ sed”医生:

您如何获得“ sed”来提取在一行中匹配的正则表达式?

换句话说,我只想删除包含行中所有不匹配字符的正则表达式对应的字符串。

我尝试使用如下所示的反向引用功能

regular expression to be isolated 
         gets `inserted` 
              here     
               |
               v  
 sed -n 's/.*\( \).*/\1/p 

这适用于某些表达式,例如

 sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p 

整齐地提取所有以'CONFIG_ ....'开头的宏名(在某些* .h文件中找到),并将它们全部逐行打印出来

          CONFIG_AT91_GPIO
          CONFIG_DRIVER_AT91EMAC
                   .
                   .   
          CONFIG_USB_ATMEL
          CONFIG_USB_OHCI_NEW
                   .
                 e.t.c. 

但是上面的东西分解为

  sed -n 's/.*\([0-9][0-9]*\).*/\1/p 

这总是返回单个数字,例如

                 7
                 9
                 .
                 .  
                 6

而不是提取一个连续的数字字段,例如。

              8908078
              89670890  
                 .
                 .  
                 .
               23019   
                 .
               e.t.c.  

PS:我将很高兴收到有关“ sed”如何实现这一目标的反馈。我知道如何做到这一点与“grep的”和“awk的”我想看看我的-尽管有限-的“sed的”理解有孔,如果有办法做到这一点在“sed的”我
有简单地被忽略了。

Answers:


22

当正则表达式包含组时,可能有不止一种方式来将字符串与之匹配:具有组的正则表达式是不明确的。例如,考虑regexp ^.*\([0-9][0-9]*\)$和字符串a12。有两种可能性:

  • 匹配a反对.*2打击[0-9]*; 1与匹配[0-9]
  • 匹配a1反对.*和空字符串对[0-9]*; 2与匹配[0-9]

与其他所有regexp工具一样,Sed应用最早的最长匹配规则:它首先尝试将第一个可变长度部分与尽可能长的字符串匹配。如果找到了将字符串的其余部分与正则表达式的其余部分匹配的方法,则可以。否则,sed尝试对第一个可变长度部分进行下一个最长匹配,然后重试。

这里,最长字符串的匹配首先是a1针对的.*,因此组仅匹配2。如果您想让小组更早开始,可以使用一些正则表达式引擎使您的.*贪婪程度降低,但是sed不具备这种功能。因此,您需要使用一些附加锚来消除歧义。指定前导.*字符不能以数字结尾,以便该组的第一位数字是第一个可能的匹配项。

  • 如果数字组不能在行的开头:

    sed -n 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p'
    
  • 如果数字组可以在行的开头,并且您的sed支持\?运算符来处理可选部分:

    sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p'
    
  • 如果数字组可以在行的开头,请遵循标准的regexp构造:

    sed -n -e 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p' -e t -e 's/^\([0-9][0-9]*\).*/\1/p'
    

顺便说一句,最早的匹配规则是最早的匹配规则,它[0-9]*匹配第一个之后的数字,而不是第二个之后的数字.*

请注意,如果一行上有多个数字序列,由于最早适用于initial的最长匹配规则,您的程序将始终提取最后一个数字序列.*。如果要提取第一个数字序列,则需要指定前面的是一个非数字序列。

sed -n 's/^[^0-9]*\([0-9][0-9]*\).*$/\1/p'

更一般而言,要提取正则表达式的第一个匹配项,您需要计算该正则表达式的取反。尽管从理论上讲这始终是可能的,但是求反的大小会随着要求反的正则表达式的大小呈指数增长,因此这通常是不切实际的。

考虑另一个例子:

sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p'

该示例实际上也存在相同的问题,但是您在典型的输入中看不到它。如果您输入它hello CONFIG_FOO_CONFIG_BAR,则上面的命令CONFIG_BAR不会打印出来CONFIG_FOO_CONFIG_BAR

有一种方法可以用sed打印第一场比赛,但这有点棘手:

sed -n -e 's/\(CONFIG_[a-zA-Z0-9_]*\).*/\n\1/' -e T -e 's/^.*\n//' -e p

(假设您的sed支持\ns替换文本中换行。)之所以可行,是因为sed寻找的是regexp的最早匹配项,而我们不尝试匹配该CONFIG_…位之前的内容。由于行内没有换行符,因此我们可以将其用作临时标记。该T命令说如果前面的s命令不匹配,则放弃。

当您不知道如何在sed中执行某些操作时,请转至awk。以下命令显示正则表达式的最早最长匹配项:

awk 'match($0, /[0-9]+/) {print substr($0, RSTART, RLENGTH)}'

而且,如果您想保持简单,请使用Perl。

perl -l -ne '/[0-9]+/ && print $&'       # first match
perl -l -ne '/^.*([0-9]+)/ && print $1'  # last match

22

虽然不是sed,但经常被忽略的一件事是grep -o,我认为这是完成此任务的更好工具。

例如,如果CONFIG_要从内核配置中获取所有参数,则可以使用:

# grep -Eo 'CONFIG_[A-Z0-9_]+' config
CONFIG_64BIT
CONFIG_X86_64
CONFIG_X86
CONFIG_INSTRUCTION_DECODER
CONFIG_OUTPUT_FORMAT

如果要获取连续的数字序列:

$ grep -Eo '[0-9]+' foo

7
sed '/\n/P;//!s/[0-9]\{1,\}/\n&\n/;D'

...将尽一切努力,尽管您可能需要n在右侧替换字段中使用文字换行符代替s。而且,顺便说一句,.*CONFIG只有在线路上只有一个比赛的情况下,事情才会起作用-否则,总会只有最后一个比赛。

你可以看到这个对于它是如何工作的描述,但因为它发生在一条线,这将在单独的行仅在比赛多次打印。

您可以使用相同的策略来获得[num]第一个出现的直线。例如,如果您只想打印CONFIG匹配项,而它仅是一行中的第三个:

sed '/\n/P;//d;s/CONFIG[[:alnum:]]*/\n&\n/3;D'

...尽管假设CONFIG每次出现的字串都被至少一个非字母数字字符分隔。

我想-对于数字来说-这也可以工作:

sed -n 's/[^0-9]\{1,\}/\n/g;s/\n*\(.*[0-9]\).*/\1/p

...关于右手的警告与以前相同\n。这甚至比第一个要快,但是显然不能像通常那样适用。

对于CONFIG事情,您可以将P;...;D上面的循环与您的模式一起使用,或者可以执行以下操作:

sed -n 's/[^C]*\(CONFIG[[:alnum:]]*\)\{0,1\}C\{0,1\}/\1\n/g;s/\(\n\)*/\1/g;/C/s/.$//p'

...只是稍微复杂一点,可以通过正确排序sed参考优先级来工作。它还可以一次性隔离所有CONFIG匹配项(尽管它确实像以前一样进行了假设),即每个CONFIG匹配项将至少由一个非字母数字字符分隔。使用GNU,sed您可以编写它:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.