sed:如果匹配模式,则打印分隔的行块

时间:2016-05-20 23:35:17

标签: bash awk sed

我想使用sed匹配由 pattern1 / pattern2 分隔的行块,然后仅在包含 pattern3 <的块上执行操作(例如,打印块) / em>的

在下面的示例中,我正在寻找&#34; 抓住我,如果你可以&#34;,在由匹配 {和} (然后我想完整地打印匹配的块)。

我尝试过的事情:

sed -n -e '/{/,/}/{1h;1!{$!{H;d};H;x;/catch me if you can/p}}'

(想法是匹配由 {} 分隔的块,然后将每个块累积到保留空间;在每个块的末尾,交换保持空间并执行匹配&#34; 如果你能&#34;)就抓住我。 这不起作用,因为所有匹配的块一起被sed视为一个块,而不是单独处理每个块。

输入数据

"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block2": {
    "bbb": "24680",
    "bar": "blah",
    "foo": "argh",
    "ccc": "135"
},
"block3": {
    "ddd": "zzz"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
}

期望的输出

"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can"
},

注1:每个块内的字段顺序是随机的。字段数和值的长度在块之间不是恒定的。我正在寻找的字段可能在某些块中丢失(而不是仅具有不同的值)。

注意2:出于教育目的,我更喜欢使用sed的解决方案,但如果不可能,则awk或bash是很好。请不要使用perl或其他工具。

参考文献:

  1. Sed command summary
  2. Sed one liners

3 个答案:

答案 0 :(得分:5)

我就是这样做的。这里有两个版本,一个用于BSD(Mac OS X)sed(也适用于其他未运行GNU sed的系统),另一个用于GNU sed

BSD sed

$ cat script.bsd-sed
/{/,/}/{
    /{/{ h; b next
    }
    /}/{ H; x; /catch me if you can/p; b next
    }
    H
    :next
}
$ sed -n -f script.bsd-sed data
"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
}
$

逻辑是:

  • 除非被告知这样做,否则不要打印任何内容(-n)。
  • 包含{}
  • 的行之间
  • 如果该行与{匹配,请将该模式复制到保留空间并跳转到标签next
  • 如果该行与}匹配,请将其添加到保留空间;切换模式并保持空间;如果图案空间(先前保持空间)与您的其他图案匹配(如果可以的话,请抓住我),打印它;跳转到标签next
  • 将该行添加到保留空间。

BSD(经典)sedb next之后不需要任何内容​​,因此行动的}位于下一行。

GNU sed

$ cat script.gnu-sed 
/{/,/}/{
    /{/{ h; b next }
    /}/{ H; x; /catch me if you can/p; b next }
    H
    :next
}
$ /opt/gnu/bin/sed -n -f script.gnu-sed data
"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
}
$

GNU sed在标签终止命令后识别分号或闭括号,因此它允许更紧凑的表示法。你甚至可以把它拼凑成一行 - 你必须添加几个分号:

$ /opt/gnu/bin/sed -n -e '/{/,/}/{ /{/{ h; b next }; /}/{ H; x; /catch me if you can/p; b next }; H; :next }' data
"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
}
$

您也可以删除不在模式匹配中的空格:

$ /opt/gnu/bin/sed -n -e '/{/,/}/{/{/{ h;b next};/}/{H;x;/catch me if you can/p;b next};H;:next}' data
"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
}
$

扩展数据文件data

"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block2": {
    "bbb": "24680",
    "bar": "blah",
    "foo": "argh",
    "ccc": "135"
},
"block3": {
    "ddd": "zzz"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
}
"block5": [
    "oops": "catch me if you can"
],
"block6": {
    "rhubarb": "dandelion"
}

答案 1 :(得分:2)

使用sed

$ sed -n '/^"/{x;/catch/p;d}; ${H;x;/catch/p;d}; H' file
"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
}

如何运作

  • -n

    除非我们询问

  • ,否则此选项会告诉sed不要打印任何内容
  • /^"/{x;/catch/p;d}

    对于以引号开头的任何行,this(1)交换模式并保留空间,(2)检查模式空间中现在的内容是否包含catch,如果是,打印它,(3)删除模式空间,sed开始在下一行工作。

  • ${H;x;/catch/p;d}

    当我们到达最后一行时,我们会做类似的事情。我们将最后一行添加到保留空间,将保留空间交换到模式空间,检查它是否包含catch,如果是,则打印它。然后删除模式空间。

  • H

    对于任何其他情况,该行将附加到保留空间。

使用awk

$ awk '/catch/{print $0 "},"}' RS='}' file
"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
,
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
},

改进

除了花括号块之外,Jonathan Leffler还增加了方括号块的可能性,如他的测试文件data所示。在sed的情况下,请尝试:

$ sed -n '/^"/{x;/{.*catch/p;d}; ${H;x;/{.*catch/p;d}; H' data
"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
}

对于awk:

$ awk '{s=(s?s"\n":"") $0} /{/{f=1} f && /catch/{f=2} /^[]}]/{if (f==2) print s; f=0; s=""} ' data
"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
}

答案 2 :(得分:1)

sed用于单独行的简单替换,全部。在40年前awk被发明时,所有比s,g和p(含-n)更多的结构都变得过时了。

使用GNU awk进行多字符RS和RT:

$ awk -v RS='},?\n' -v ORS= '/catch me if you can/{print $0 RT}' file
"block1": {
    "foo": "abcd",
    "bar": "catch me if you can",
    "aaa": "12345"
},
"block4": {
    "foo": "xyz",
    "bar": "catch me if you can",
}