Awk 正则表达式更新多行字符串并保留缩进

Awk 正则表达式更新多行字符串并保留缩进,awk,sed,yaml,pyyaml,Awk,Sed,Yaml,Pyyaml,我有数百个YAML文件需要不时更新 更新前: sss: - ccc: brr: 'mmm' jdk: 'openjdk8' - bbb: brr: 'rel/bbb' jdk: 'openjdk8' - aaa: brr: 'rel/aaa' jdk: 'openjdk7' 更新后: sss: - ddd: brr: 'mmm' jdk: 'openjdk8' - ccc:

我有数百个YAML文件需要不时更新

更新前:

sss:
  - ccc:
      brr: 'mmm'
      jdk: 'openjdk8'
  - bbb:
      brr: 'rel/bbb'
      jdk: 'openjdk8'
  - aaa:
      brr: 'rel/aaa'
      jdk: 'openjdk7'
更新后:

sss:
  - ddd: 
      brr: 'mmm'
      jdk: 'openjdk8'
  - ccc:
      brr: 'rel/ccc'
      jdk: 'openjdk8'
  - bbb:
      brr: 'rel/bbb'
      jdk: 'openjdk8'
  - aaa:
      brr: 'rel/aaa'
      jdk: 'openjdk7'
  • 对于任何文件中出现的以下模式:
  • 替换并修改上述模式,以将
    'mmm'
    替换为
    'rel/ccc'
  • 以以下格式创建新的子字符串(多行):
  • 合并2。三,。并将原始文件替换为:
  • 例如,我们需要更新上面的文件,使其看起来像是保留每行中的空格/制表符,因为格式化对于YAML很重要


    我已经用PyYAML尝试过了,但是由于语法的复杂性,它不起作用。这可以通过使用awk,sed捕获空白来实现吗?

    尝试类似于
    awk
    程序的方法:

    /sss:/ { sss = 1; }
    /- ccc:/ { ccc = 1; ind = substr($0, 1, index($0, "-")-1); next; } # don't print
    $1 == "brr:" && $2 == "'mmm'" {
        if (sss && ccc) {
            print ind "- ddd:";
            print ind "    brr: 'mmm'";
            print ind "    jdk: 'openjdk8'";
            print ind "- ccc:";
            print ind "    brr: 'rel/ccc'";
            sss = 0; ccc = 0;
        }
        next;
    }
    { print }
    

    第一条规则用于标记进入
    sss
    块,第二条规则用于标记
    ccc
    块,并记录压痕深度。第三条规则添加新的和修改的数据,根据记录的深度缩进,然后退出
    sss
    ccc
    块。最后一条规则打印刚刚读取的行。第二条和第三条规则中的
    next
    语句阻止应用以下所有规则。

    尝试类似的
    awk
    程序:

    /sss:/ { sss = 1; }
    /- ccc:/ { ccc = 1; ind = substr($0, 1, index($0, "-")-1); next; } # don't print
    $1 == "brr:" && $2 == "'mmm'" {
        if (sss && ccc) {
            print ind "- ddd:";
            print ind "    brr: 'mmm'";
            print ind "    jdk: 'openjdk8'";
            print ind "- ccc:";
            print ind "    brr: 'rel/ccc'";
            sss = 0; ccc = 0;
        }
        next;
    }
    { print }
    

    第一条规则用于标记进入
    sss
    块,第二条规则用于标记
    ccc
    块,并记录压痕深度。第三条规则添加新的和修改的数据,根据记录的深度缩进,然后退出
    sss
    ccc
    块。最后一条规则打印刚刚读取的行。第二条和第三条规则中的
    next
    语句阻止应用以下所有规则。

    仅使用正则表达式解析结构化数据(无论是YAML、HTML、XML还是CSV)仅在极少数可能情况下有效。使用YAML多行标量,以通用方式处理流样式和块样式等几乎是不可能的。如果不是这样的话,可能已经有人用awk编写了完整的YAML解析器。(awk没有什么问题,只是它不是处理YAML的正确工具)

    这并不意味着您不能使用正则表达式查找特定元素,您只需要做一些准备:

    import sys
    import re
    import ruamel.yaml
    
    yaml_str = """\
    sss:
      - ccc:
          brr: 'mmm'
          jdk: 'openjdk8'
      - bbb:
          brr: 'rel/bbb'
          jdk: 'openjdk8'
      - aaa:
          brr: 'rel/aaa'
          jdk: 'openjdk7'
    """
    
    
    class Paths:
        def __init__(self, data, sep=':'):
            self._sep = sep
            self._data = data
    
        def walk(self, data=None, prefix=None):
            if data is None:
                data = self._data
            if prefix is None:
                prefix = []
            if isinstance(data, dict):
                for idx, k in enumerate(data):
                    path_list = prefix + [k]
                    yield self._sep.join([str(q) for q in path_list]), path_list, idx, data[k]
                    for x in self.walk(data[k], path_list):
                        yield x
            elif isinstance(data, list):
                for idx, k in enumerate(data):
                    path_list = prefix + [idx]
                    yield self._sep.join([str(q) for q in path_list]), path_list, idx, k
                    for x in self.walk(k, path_list):
                        yield x
    
        def set(self, pl, val):
            pl = pl[:]
            d = self._data
            while(len(pl) > 1):
                d = d[pl.pop(0)]
            d[pl[0]] = val
    
        def insert_in_list(self, pl, idx, val):
            pl = pl[:]
            d = self._data
            while(len(pl) > 1):
                d = d[pl.pop(0)]
            d.insert(idx, val)
    
    
    data = ruamel.yaml.round_trip_load(yaml_str, preserve_quotes=True)
    paths = Paths(data)
    pattern = re.compile('sss:.*:c.*:brr$')
    # if you are going to insert/delete use list(paths.walk())
    for p, pl, idx, val in list(paths.walk()):
        print('path', p)
        if not pattern.match(p):
            continue
        paths.set(pl, ruamel.yaml.scalarstring.SingleQuotedScalarString('rel/ccc'))
        paths.insert_in_list(pl[:-2], idx, {'new': {
            'brr': ruamel.yaml.scalarstring.SingleQuotedScalarString('mmm'),
            'jdk': ruamel.yaml.scalarstring.SingleQuotedScalarString('openjdk8')
            }})
    
    print('----------')
    
    ruamel.yaml.round_trip_dump(data, sys.stdout)
    
    其输出为:

    path sss
    path sss:0
    path sss:0:ccc
    path sss:0:ccc:brr
    path sss:0:ccc:jdk
    path sss:1
    path sss:1:bbb
    path sss:1:bbb:brr
    path sss:1:bbb:jdk
    path sss:2
    path sss:2:aaa
    path sss:2:aaa:brr
    path sss:2:aaa:jdk
    ----------
    sss:
    - new:
        brr: 'mmm'
        jdk: 'openjdk8'
    - ccc:
        brr: 'rel/ccc'
        jdk: 'openjdk8'
    - bbb:
        brr: 'rel/bbb'
        jdk: 'openjdk8'
    - aaa:
        brr: 'rel/aaa'
        jdk: 'openjdk7'
    
  • 打印“路径”不是必要的,但在这里可以更好地了解正在发生的事情

  • SingleQuotedScalarString对于在YAML输出中获取字符串标量周围的多余引号是必需的

  • dict子类由
    ruamel.YAML
    加载YAML映射,它支持Python 2.7和Python 3.5及更高版本的
    .insert(index,key,val)
    ,因此您也可以在映射的特定位置插入


  • 单独使用正则表达式解析结构化数据(无论是YAML、HTML、XML还是CSV)只在可能的情况下的一小部分中起作用。使用YAML多行标量,以通用方式处理流样式和块样式等几乎是不可能的。如果不是这样的话,可能已经有人用awk编写了完整的YAML解析器。(awk没有什么问题,只是它不是处理YAML的正确工具)

    这并不意味着您不能使用正则表达式查找特定元素,您只需要做一些准备:

    import sys
    import re
    import ruamel.yaml
    
    yaml_str = """\
    sss:
      - ccc:
          brr: 'mmm'
          jdk: 'openjdk8'
      - bbb:
          brr: 'rel/bbb'
          jdk: 'openjdk8'
      - aaa:
          brr: 'rel/aaa'
          jdk: 'openjdk7'
    """
    
    
    class Paths:
        def __init__(self, data, sep=':'):
            self._sep = sep
            self._data = data
    
        def walk(self, data=None, prefix=None):
            if data is None:
                data = self._data
            if prefix is None:
                prefix = []
            if isinstance(data, dict):
                for idx, k in enumerate(data):
                    path_list = prefix + [k]
                    yield self._sep.join([str(q) for q in path_list]), path_list, idx, data[k]
                    for x in self.walk(data[k], path_list):
                        yield x
            elif isinstance(data, list):
                for idx, k in enumerate(data):
                    path_list = prefix + [idx]
                    yield self._sep.join([str(q) for q in path_list]), path_list, idx, k
                    for x in self.walk(k, path_list):
                        yield x
    
        def set(self, pl, val):
            pl = pl[:]
            d = self._data
            while(len(pl) > 1):
                d = d[pl.pop(0)]
            d[pl[0]] = val
    
        def insert_in_list(self, pl, idx, val):
            pl = pl[:]
            d = self._data
            while(len(pl) > 1):
                d = d[pl.pop(0)]
            d.insert(idx, val)
    
    
    data = ruamel.yaml.round_trip_load(yaml_str, preserve_quotes=True)
    paths = Paths(data)
    pattern = re.compile('sss:.*:c.*:brr$')
    # if you are going to insert/delete use list(paths.walk())
    for p, pl, idx, val in list(paths.walk()):
        print('path', p)
        if not pattern.match(p):
            continue
        paths.set(pl, ruamel.yaml.scalarstring.SingleQuotedScalarString('rel/ccc'))
        paths.insert_in_list(pl[:-2], idx, {'new': {
            'brr': ruamel.yaml.scalarstring.SingleQuotedScalarString('mmm'),
            'jdk': ruamel.yaml.scalarstring.SingleQuotedScalarString('openjdk8')
            }})
    
    print('----------')
    
    ruamel.yaml.round_trip_dump(data, sys.stdout)
    
    其输出为:

    path sss
    path sss:0
    path sss:0:ccc
    path sss:0:ccc:brr
    path sss:0:ccc:jdk
    path sss:1
    path sss:1:bbb
    path sss:1:bbb:brr
    path sss:1:bbb:jdk
    path sss:2
    path sss:2:aaa
    path sss:2:aaa:brr
    path sss:2:aaa:jdk
    ----------
    sss:
    - new:
        brr: 'mmm'
        jdk: 'openjdk8'
    - ccc:
        brr: 'rel/ccc'
        jdk: 'openjdk8'
    - bbb:
        brr: 'rel/bbb'
        jdk: 'openjdk8'
    - aaa:
        brr: 'rel/aaa'
        jdk: 'openjdk7'
    
  • 打印“路径”不是必要的,但在这里可以更好地了解正在发生的事情

  • SingleQuotedScalarString对于在YAML输出中获取字符串标量周围的多余引号是必需的

  • dict子类由
    ruamel.YAML
    加载YAML映射,它支持Python 2.7和Python 3.5及更高版本的
    .insert(index,key,val)
    ,因此您也可以在映射的特定位置插入


  • 你可以看一看。在这里,您可以看到如何使用变量在多个模式之间“切换awk解析”。您需要提供有关替换/添加的更多信息。添加第二级不存在的内容很容易理解。例:您将其放在章节的开头,但是否可以将其添加到同一章节的末尾?我们是否可以假设从示例2中删除
    sss:
    是一个错误?您捕获空白是什么意思?
    brr:'mmm'
    的缩进级别是否足以识别所有需要替换的位置?@Anthon:sss可以删除,但必须用作搜索字符串的起点。缩进可能不同,因此,我想捕获空格/制表符,并在更新的多行字符串中重新引入它们。在这里,您可以看到如何使用变量在多个模式之间“切换awk解析”。您需要提供有关替换/添加的更多信息。添加第二级不存在的内容很容易理解。例:您将其放在章节的开头,但是否可以将其添加到同一章节的末尾?我们是否可以假设从示例2中删除
    sss:
    是一个错误?您捕获空白是什么意思?
    brr:'mmm'
    的缩进级别是否足以识别所有需要替换的位置?@Anthon:sss可以删除,但必须用作搜索字符串的起点。缩进可能不同,因此,我希望捕获空格/制表符,并在更新的多行字符串中重新引入它们。输出是正确的,但没有缩进。缩进可以保留吗?没有缩进是什么意思?根据我对你的评论的理解,我已经更新了我的答案。输出是正确的,但没有缩进。缩进可以保留吗?没有缩进是什么意思?我已经根据我的理解更新了我的答案
    path sss
    path sss:0
    path sss:0:ccc
    path sss:0:ccc:brr
    path sss:0:ccc:jdk
    path sss:1
    path sss:1:bbb
    path sss:1:bbb:brr
    path sss:1:bbb:jdk
    path sss:2
    path sss:2:aaa
    path sss:2:aaa:brr
    path sss:2:aaa:jdk
    ----------
    sss:
    - new:
        brr: 'mmm'
        jdk: 'openjdk8'
    - ccc:
        brr: 'rel/ccc'
        jdk: 'openjdk8'
    - bbb:
        brr: 'rel/bbb'
        jdk: 'openjdk8'
    - aaa:
        brr: 'rel/aaa'
        jdk: 'openjdk7'