sed:提取在一行中出现任意次数的模式中的文本

sed:提取在一行中出现任意次数的模式中的文本

我需要提取每行中可能出现 1 到 n 次的字符串部分。

例如,这将反映我所需要的:

This [dbo].[something] is a text containing [dbo].[something_else], then okay?
And then, [dbo].[something] may appear just once.
But why, nothing prevents [dbo].[something] from appearing twice as [dbo].[something] here.
And then can be three times, as [dbo].[something] is [dbo].[anything] but [dbo].[elsewhere] here.
[dbo].[otherthing] depicts another scenario with just one and pattern heading line
Or, also [dbo].[ultra] with an arbitrary amount of [dbo].[references] but ending with [dbo].[pattern]

您可能已经注意到,该模式将是\[dbo\]\.\[[^]]+\].例如,从上面的文本中,我想要一个结果:

something something_else
something
something something
something anything elsewhere
otherthing
ultra references pattern

然后我可以内联所有内容(或附加到 bash 数组)并过滤重复项,这不应该是问题。我只是很难弄清楚如何在一次扫描中完成这个过滤器。

我在这里得到的结果是仅提取最后一个匹配项(当您习惯了 sed 的“贪婪”模式匹配方法时,很明显为什么会这样):

cat dborefs.txt | sed -E "s/(.*\[dbo\]\.\[([^]]+)\].*)*/\2/g"
something_else
something
something
elsewhere
otherthing
pattern

我可以提取,然后替换模式,使它们不再匹配,然后再次提取,直到不再匹配,但这听起来太麻烦了,考虑到所有 bash 开销;最好能够在一次调用中提取所有内容sed。我觉得这应该是可能的,只是无法轻易弄清楚如何实现。我认为这可能对其他人有用,因此我觉得在这里分享这个问题可以为社区带来丰硕的成果。

答案1

要获取以换行符分隔的标记字符串列表:

$ grep -o '\[dbo\]\.\[[^]]*\]' file | cut -d . -f 2 | tr -d '[]'
something
something_else
something
something
something
something
anything
elsewhere
otherthing
ultra
references
pattern

第一个grep仅生成带有[dbo].[word].为cut我们提供了[word]位,而从其中tr删除了[和。]

要获取按它们出现的行分组的标记字符串:

$ sed -e 's/\][^.[]*\[/] [/g' -e 's/^[^[]*//' -e 's/[^]]*$//' -e 's/\[dbo\]\.\[\([^]]*\)\]/\1/g' file
something something_else
something
something something
something anything elsewhere
otherthing
ultra references pattern

这里使用的四个替换是

  1. 删除]和之间[不是点或 a 的所有内容[(实际上,用空格替换;这些是最终输出中的空格)。
  2. 删除第一个之前的所有内容[
  3. 删除最后一个之后的所有内容]
  4. 提取剩余内容中标记的单词。

答案2

目前,我可以(希望)比重复调用 sed 更好的方法是用希望不会出现在文件中的占位符“链接”替换。

cat dborefs.txt | sed -E "
 s/\[dbo\]\.\[([^]]+)\]/_-\1-_/g;
 s/(^|-_)([^_]+|_[^-])*(\$|_-)/ /g;
 s/(^ +| +\$)//g"

换句话说:

  • 首先我得到所有[dbo].[<extract>]并替换为_-<extract>-_;
  • 然后将第一个之前_-、之间-_以及_-最后一个之后的任何文本替换-_为单个空白字符;
  • 然后清理每行开头和结尾的空白字符。

这给出了所需的结果,我可以将其全部连接到一个数组中,然后过滤sort唯一的条目。但我仍然认为应该有一种更好的方法,无需链式sed命令。

答案3

您可以在 Perl 中更轻松地完成此操作,使用散列(关联数组)来唯一化匹配:

$ perl -nE 'while ($_ =~ /\[dbo\]\.\[(.*?)\]/g) {$h{$1}++} }{ for $k (keys %h) {say $k}' dborefs.txt 
otherthing
anything
elsewhere
something
pattern
something_else
ultra
references

通过重复应用该函数,GNU Awk 中可以采用类似的方法match

$ gawk '{
    while (match($0,/\[dbo\]\.\[([^]]+)\]/,a)) {h[a[1]]++; $0 = substr($0,RSTART+RLENGTH)}
  } 
  END{
    for (k in h) print k
  }' dborefs.txt 
references
elsewhere
something
something_else
pattern
otherthing
anything
ultra

对于其他 Awk 实现,其match函数不提供捕获组数组,您需要修剪匹配:

while (match($0,/\[dbo\]\.\[([^]]+)\]/)) {h[substr($0,RSTART+7,RLENGTH-8)]++; $0 = substr($0,RSTART+RLENGTH)}

答案4

还有另一种方法,这次使用多个实用程序。管道的 sed 部分提取模式,而 awk 部分对其进行唯一化,同时保留它们首次出现的顺序。

sed -Ee '
  /\n/{P;D;}
  s/\[dbo]\.\[([^]]+)]/\n\1\n/;D
' dborefs.txt | awk '!a[$0]++'

相关内容