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

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

致所有“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”中做到这
一点根本就被忽视了。

答案1

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

  • 对抗和对抗;a​匹配的是..*2[0-9]*1[0-9]
  • 匹配a1针对.*和空字符串针对[0-9]*2由匹配[0-9]

与所有其他正则表达式工具一样,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'
    
  • 如果数字组可以位于行的开头,请遵循标准正则表达式结构:

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

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

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

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 支持在替换文本\n中表示换行符s。)这是有效的,因为 sed 查找正则表达式的最早匹配项,并且我们不会尝试匹配该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

答案2

虽然不是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

答案3

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

...将毫不费力地做到这一点,尽管您可能需要文字换行符来代替n右侧替换字段中的 s 。顺便说一句,.*CONFIG只有当线上只有一场比赛时,这件事才会起作用——否则它总是只能得到最后一场。

你可以看到有关其工作原理的描述,但这只会在单独的行上打印与在一行中出现的次数相同的匹配项。

您可以使用相同的策略来获取[num]一行中的第 th 次出现。例如,如果您只想打印 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 匹配将由至少一个非字母数字字符分隔。使用 GNUsed你可以这样写:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'

相关内容