【问题标题】:Extract the string between nth pattern1 and mth pattern2 - sed/awk提取第 n 个模式 1 和第 m 个模式 2 之间的字符串 - sed/awk
【发布时间】:2020-08-05 04:58:21
【问题描述】:

在下面的示例中,如何返回第 n 个模式 1 和第 m 个模式 2 之间的 字符串,其中模式 1 和模式 2 在字符串中可能出现多次?

更好的例子:

zz this is string xx zz another string xx third string zz xx and a tail xx

在第二个 zz 和第三个 xx 之间你会如何返回?

another string xx third string zz

编辑:对于任何寻找这个的人来说,正则表达式中的“捕获组”和“前向/后向引用”似乎是此任务所需的术语..Helpful information here

以上编辑

对不起最初的助手。你的答案很好,我的例子选得不好。

请随时从此处删除以整理此问题。为了提供的原始答案的完整性,我只是留下它。

糟糕的原始问题和示例:

echo '1a 2b 3c 4d 5e 6f 7g 8h 9i 0j'

如果你能想出一个没有尾随空格的解决方案,那就加分。我知道可以通过管道再次删除前导/尾随空格,但我很好奇是否有更整洁的解决方案。我期望的输出(不包括单引号)是:

'3c 4d 5e 6f'' 3c 4d 5e 6f '

  • 我正在使用 sed/awk,因此希望它可以扩展到匹配 pattern1 的第 n 次出现和 pattern2 的第 m 次出现,而不是仅仅匹配单个字符(空格) .

我尝试了一些变体。我相信这是最接近 sed 的正确方法:

echo '1 2 3 4 5 6 7 8 9 0' | sed -n 's/.*[ ]{2}.*[ ]{4}.*/\1/p'

但是,它返回错误:

sed -e expression #1, char 28: invalid reference \1 on `s' command's RHS

【问题讨论】:

  • 会的。我现在基于两个快速正确的答案意识到这个例子是不够的。我会补充一些更合适的。
  • 一些带有一些边缘情况的例子会有所帮助。现在很难理解你真正想要做什么。
  • 希望编辑后的问题和示例更好。我认为“捕获组”是我可能追求的,正如我第一次回答我糟糕的措辞时提到的那样。感谢那些已经做出贡献的人。
  • echo '1 2 3 4 5 6 7 8 9 0' | awk '{for (i=3;i<=6;i++) printf (i==3)?"%s":" %s",$i; print""}' ??

标签: regex awk sed


【解决方案1】:

perl

$ s='zz this is string xx zz another string xx third string zz xx and a tail xx'

$ echo "$s" | perl -pe 's/((.*?xx){3}).*/$1/'
zz this is string xx zz another string xx third string zz xx

$ echo "$s" | perl -pe 's/((.*?xx){3}).*/$1=~s#(.*?zz){2}\s*|\s*xx$##gr/e'
another string xx third string zz

第一个命令s/((.*?xx){3}).*/$1/ 显示了如何使xx 出现第三次,其中.*? 是非贪婪匹配以尽可能减少消耗。

e 标志允许在替换部分中使用 Perl 代码,因此您可以修改此字符串以删除最多第二次出现的 zz 和最后一次出现的 xx$1=~s#(.*?zz){2}\s*|\s*xx$##gr

【讨论】:

  • 感谢 Sundeep,这看起来可以完成这项工作。 Perl 对我来说是新的,所以我需要一些时间来消化它,但我很感激这个答案。
  • @Chris 当然,请参阅我的perl one-liners chapter 以获取示例和解释
【解决方案2】:

在每个 UNIX 机器上的任何 shell 中使用任何 awk:

$ cat tst.awk
BEGIN {
    n = 2
    m = 3
}
{
    $0 = encode($0)
    beg = match($0,"([^<]*<){"n"}") + RLENGTH
    end = match($0,"([^>]*>){"m-1"}[^>]+") + RLENGTH
    print decode(substr($0,beg,end-beg))
}

function encode(str) {
    gsub(/@/,"@A",str); gsub(/</,"@B",str); gsub(/>/,"@C",str)
    gsub(/zz/,"<",str); gsub(/xx/,">",str)
    return str
}

function decode(str) {
    gsub(/>/,"xx",str); gsub(/</,"zz",str)
    gsub(/@C/,">",str); gsub(/@B/,"<",str); gsub(/@A/,"@",str)
    return str
}

.

$ awk -f tst.awk file
 another string xx third string zz

encode()decode() 函数是如何将您感兴趣的字符串转换为输入中任何位置都不存在的单个字符,以便您可以在 match() 中使用的括号表达式中否定它们来电。

【讨论】:

  • 谢谢。我决定接受 sed 解决方案作为答案,只是因为它不那么冗长,但这可能具有可移植性优势,所以我肯定会坚持使用这个!
【解决方案3】:

使用普通的 GNU sed:

pat1='zz'
n=2
pat2='xx'
m=3

echo 'zz this is string xx zz another string xx third string zz xx and a tail xx' |
sed "s/$pat1/\n/$n; s/$pat2/\n/$m; s/[^\n]*\n//; s/\n.*//"

输出

 another string xx third string zz    

s/$pat1/\n/$n$nth $pat1 替换为换行符。
s/$pat2/\n/$m$mth $pat2 替换为换行符。
s/[^\n]*\n// 删除两者之间的部分字符串的开头和第一个换行符(包括)。
s/\n.*// 删除换行符(包括)和字符串结尾之间的部分。

注意sed 命令可以稍微简化为sed -E "s/$pat1/\n/$n; s/$pat2/\n/$m; s/.*\n(.*)\n.*/\1/"

【讨论】:

  • 聪明的实现。谢谢:-)
猜你喜欢
  • 1970-01-01
  • 2013-02-23
  • 2022-12-18
  • 1970-01-01
  • 2012-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多