簡體   English   中英

在bash中跨多行匹配正則表達式

[英]Match regex across multiple lines in bash

我想匹配文件中以[%開頭並以%]結尾的所有模式。

我嘗試了多種工具,例如 awk、sed、pcregrep,但它們似乎都不起作用,盡管它們被建議作為類似問題的最佳答案。

[% FOREACH selection = selections -%]
      case SELECTION_ID_[% SELECTION_NAME %]: {
        const [% selectionType %]& source = this->[% selectionName %]();
        rc = bcem_AggregateUtil::toAggregate(result,
                                             d_selectionId,
                                             source);
      } break;
[% END -%]

[% foo ]

[% INCLUDE attributeSearchBlock

    tree=attributeSearchTree depth=0

    visit='ReturnAttributeInfo' name='name' nameLength='nameLength' -%]

對於上面的代碼,我希望得到以下結果:

[% FOREACH selection = selections -%]
      case SELECTION_ID_[% SELECTION_NAME %]: {
        const [% selectionType %]& source = this->[% selectionName %]();
[% END -%]
[% INCLUDE attributeSearchBlock

    tree=attributeSearchTree depth=0

    visit='ReturnAttributeInfo' name='name' nameLength='nameLength' -%]

但我正在匹配所有的行。

我究竟做錯了什么?

稍后編輯:

如果它在多行上,它也應該匹配。 例如:

[% foo
bar -%]

后期編輯 2:似乎沒有一個答案有效,所以我使用以下方法手動完成了整個過程:

        hasPatternStarted=false
        while read -r line; do
            if [[ $line =~ '[%' ]]; then
                hasPatternStarted=true
            fi
            if [[ $line =~ '%]' ]]; then
                hasPatternStarted=false
                echo $line
            fi
            if [ "$hasPatternStarted" = true ]; then
                echo $line
            fi
        done < "$filename"

它工作正常,但如果有人有一個單線來解決這個問題(使用 sed、awek、pcregrep、perl、grep 任何東西),請這么說。

如果你看看你的要求,你會得到兩行,因為只有兩個結尾是-%]

 awk '/\[%.*-%\]/' file
[% FOREACH selection = selections -%]
[% END -%]

你可以做到這一點得到了所有開始與結果[%與末端%]

awk '/\[%.*%\]/' file
[% FOREACH selection = selections -%]
      case SELECTION_ID_[% SELECTION_NAME %]: {
        const [% selectionType %]& source = this->[% selectionName %]();
[% END -%]

這是將 GNU awk 用於多字符 RS 和 RT 的一種方法:

$ awk -v RS='%]' -v ORS= '{print gensub(/.*(\n[^\n]*\[%)/,"\\1",1) RT}' file
[% FOREACH selection = selections -%]
      case SELECTION_ID_[% SELECTION_NAME %]
        const [% selectionType %]& source = this->[% selectionName %]
[% END -%]
[% INCLUDE attributeSearchBlock

    tree=attributeSearchTree depth=0

    visit='ReturnAttributeInfo' name='name' nameLength='nameLength' -%]

這是另一個帶有多字符 RS 和 FPAT 的:

$ cat tst.awk
BEGIN {
    RS = "^$"
    FPAT = "[^\n]*{[^{}]*}"
}
{
    gsub(/@/,"@A"); gsub(/{/,"@B"); gsub(/}/,"@C")
    gsub(/\[%/,"{")
    gsub(/%\]/,"}")
    for (i=1; i<=NF; i++) {
        str = $i
        gsub(/}/,"%]",str)
        gsub(/{/,"[%",str)
        gsub(/@C/,"}",str); gsub(/@B/,"{",str) gsub(/@A/,"@",str)
        print str
    }
}

$ awk -f tst.awk file
[% FOREACH selection = selections -%]
      case SELECTION_ID_[% SELECTION_NAME %]
        const [% selectionType %]& source = this->[% selectionName %]
[% END -%]
[% INCLUDE attributeSearchBlock

    tree=attributeSearchTree depth=0

    visit='ReturnAttributeInfo' name='name' nameLength='nameLength' -%]

第二個腳本演示了使用 awk 或 sed 等僅支持貪婪匹配的工具時的常見習慣用法,但您需要在多字符字符串之間匹配文本,即將這些多字符分隔符字符串轉換為單個字符,以便您可以使用它們之間的否定字符類。

所以在上面有:

gsub(/@/,"@A"); gsub(/{/,"@B"); gsub(/}/,"@C")

我將所有@ s 轉換為@A s 以釋放@字符,然后將所有{ s 轉換為@B s(現在我們知道輸入中不會出現這個字符串,因為我們只是在每個 @ 后面放了一個 A ) 然后將所有} s 轉換為@C s,從而確保輸入中沒有{}字符,因此將它們釋放出來供我們用作正則表達式分隔符。 我現在可以這樣做:

gsub(/\[%/,"{")
gsub(/%\]/,"}")

將真正的分隔符字符串轉換為字符,以便我可以在正則表達式中使用它們的否定來匹配這些分隔符之間的字符串:

FPAT = "{[^{}]*}"

在 GNU awk 中,像這樣分配 FPAT 會自動將匹配的字符串保存在 $1、$2 等中,所以我只需要在打印每個字段之前展開上述替換,因此:

gsub(/}/,"%]",str)
gsub(/{/,"[%",str)
gsub(/@C/,"}",str); gsub(/@B/,"{",str) gsub(/@A/,"@",str)

對於任何 POSIX awk,與上面的第二個腳本等效的是:

$ cat tst.awk
{ rec = (NR>1 ? rec ORS : "") $0 }
END {
    $0 = rec
    FPAT = "[^\n]*[{][^{}]*[}]"
    gsub(/@/,"@A"); gsub(/[{]/,"@B"); gsub(/[}]/,"@C")
    gsub(/\[%/,"{")
    gsub(/%\]/,"}")
    while ( match($0,FPAT) ) {
        str = substr($0,RSTART,RLENGTH)
        $0 = substr($0,RSTART+RLENGTH)
        gsub(/[}]/,"%]",str)
        gsub(/[{]/,"[%",str)
        gsub(/@C/,"}",str); gsub(/@B/,"{",str) gsub(/@A/,"@",str)
        print str
    }
}

$ awk -f tst.awk file
[% FOREACH selection = selections -%]
      case SELECTION_ID_[% SELECTION_NAME %]
        const [% selectionType %]& source = this->[% selectionName %]
[% END -%]
[% INCLUDE attributeSearchBlock

    tree=attributeSearchTree depth=0

    visit='ReturnAttributeInfo' name='name' nameLength='nameLength' -%]

TL;DR: perl -ne 'print if /\\[%/../%\\]/' file

你認為你可以這樣做: sed -n '/[%/,/%]/p'但它沒有正確終止內聯。

因此,您可以將上述內容轉換為 perl: perl -ne 'print if /\\[%/.../%\\]/'並且由於...運算符而具有相同的問題。

但是,Perl 有一個操作符可以節省時間: perl -ne 'print if /\\[%/../%\\]/'

正如perlop所說:

在標量上下文中,“..”返回一個布爾值。 該運算符是雙穩態的,就像觸發器一樣,模擬 sed、awk 和各種編輯器的行范圍(逗號)運算符。 每個“..”運算符都維護自己的布爾狀態,即使調用包含它的子程序也是如此。 只要它的左操作數為假,它就是假的。 一旦左操作數為真,范圍運算符保持為真,直到右操作數為真,之后范圍運算符再次變為假。 直到下次評估范圍運算符時它才會變為假。 它可以測試正確的操作數並在它變為真的同一評估中變為假(如在 awk 中),但它仍然返回一次真。 如果您不希望它在下一次評估之前測試正確的操作數,如在 sed 中,只需使用三個點 ("..." ) 而不是兩個。 在所有其他方面,“...”的行為就像“..”一樣。

所有這一切:對於行范圍操作,使用 perl 你可以同時擁有它,因為.. (如 awk)和... (如 sed)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM