删除等号后的多行字符串

Remove mutiline string after equal sign

我正在尝试从 AWS 获取 IAM 策略并将其添加到以下特定格式的文本文件中。我想删除文件中“}”括号结束前的 policy= 之后的所有内容。

这是我的文本文件样本。但是原始文件可以在同一个文件中有多个 example_policy 实例。

"example1_policy" {
    name="example"
    policy=jsonencode(
        {
            Statement=[
                {
                    Action=[
                        s3:*
                    ]
                    Effect="Allow"
                },
            ]
            Version="2012-10-17"
        }
    )
}
"example2_policy" {
    name="example2"
    policy=jsonencode(
        {
            Statement=[
                {
                    Action=[
                        s3:*
                    ]
                    Effect="Allow"
                },
            ]
            Version="2012-10-17"
        }
    )
}
"example3_policy" {
    name="example3"
    policy=jsonencode(
        {
            Statement=[
                {
                    Action=[
                        s3:*
                    ]
                    Effect="Allow"
                },
            ]
            Version="2012-10-17"
        }
    )
}

预期输出:

"example1_policy" {
    name="example1"
    policy=
}
"example2_policy" {
    name="example2"
    policy=
}
"example3_policy" {
    name="example3"
    policy=
}

"example1_policy" {
    name="example1"
    policy=<placeholder>
}
"example2_policy" {
    name="example2"
    policy=<placeholder>
}
"example3_policy" {
    name="example3"
    policy=<placeholder>
}

根据@Wiktor 的评论,我尝试了这个命令

sed -i '/policy=/,/^\s*)\s*$/d' test.txt

输出:policy= 应该保持不变。

"example_policy" {
    name="example"
}

你可以在 Python 中很容易地做到这一点,因为你有状态:

def clean(s, pattern='policy='):
  pre, post = s.split(pattern)
  harmony = True
  quote = False
  braces = 0
  for i, char in enumerate(post):
    if harmony and char == '\n':
      return f'{pre}{pattern}{post[i:]}'
    if not quote:
      if char == '(':
        braces += 1
      elif char == ')':
        braces -= 1
      elif char in ('"', "'"):
        quote = char
      harmony = braces == 0
    elif quote == char:
      quote = False

这甚至会忽略包含在字符串(" 和 ' 字符串)中的大括号。

所以,这个版本也适用于更棘手的字符串:

"example_policy" {
    name="example"
    policy=jsonencode(
        {
            Statement=[
                {
                    Action=[
                        s3:*
                    ]
                    Effect="Al)l'ow"
                },
            ]
            Version='"2012-10)-17"'
        }
    )
}

您可以轻松扩展它以支持其他类型的大括号或引号。唯一的区别是你需要为大括号使用计数器,因为开始和结束字符不同,而对于引号,你只需要在匹配列表中添加额外的字符——因为引号中的其他所有内容都被忽略,你只需要记住报价是用哪个字符打开的。

用正则表达式做这件事会比较麻烦,因为它们只支持有限大括号嵌套。

要删除同一个字符串中的多个策略,我们需要定义一个辅助函数:

def clean(s):
  harmony = True
  quote = False
  braces = 0
  for i, char in enumerate(s):
    if harmony and char == '\n':
      return s[i:]
    if not quote:
      if char == '(':
        braces += 1
      elif char == ')':
        braces -= 1
      elif char in ('"', "'"):
        quote = char
      harmony = braces == 0
    elif quote == char:
      quote = False
  return s

然后是将“清洁助手”应用于每个单独块的主要功能:

def clean_all(s, pattern='policy='):
  head, *tail = s.split(pattern)
  return f'{head}{pattern}{pattern.join(clean(part) for part in tail)}'

您可以使用以下 GNU sed 命令:

sed -i '/policy=/,/^\s*)\s*$/{/policy=/!d};s/\(policy=\).*/<placehlder>/' file

参见online demo详情:

  • /policy=/,/^\s*)\s*$/ - 查找带有 policy= 的行和仅包含一个 ) 字符并用零个或多个空格
  • 包围的行之间的行块
  • {/policy=/!d} - 防止删除找到的块中的第一行并删除其他行
  • s/\(policy=\).*/<placehlder>/ - 将 policy= 之后的所有内容替换为 <placeholder>

如果需要匹配 policy=,那么任何字符到 ( 然后 到下一个平衡的 ) char,你可以使用perl命令,如

perl -0777 -i -pe 's/^(\h*policy=)[^()]*(\((?:[^()]++|(?2))*\))/<placehlder>/gm' file

详情:

  • -0777 - 将文件读入单个字符串,以便换行符和行
  • -i - 内联文件更改
  • ^ - 行首(由于 m 标志)
  • (\h*policy=) - 第 1 组 (</code>):零个或多个水平空格</li> <li><code>[^()]* - ()
  • 以外的零个或多个字符
  • (\((?:[^()]++|(?2))*\)) - 第 2 组(用于递归):\( - 一个 ( 字符,(?:[^()]++|(?2))* - 一个或多个字符的零个或多个序列 () 或整个第 2 组模式递归,并且 \) 匹配 ) char
  • <placehlder> - 替换为组 1 + <placeholder> 字符串

online demo and the regex demo:

#!/bin/bash
s='"example1_policy" {
    name="example"
    policy=jsonencode(
        {
            Statement=[
                {
                    Action=[
                        s3:*
                    ]
                    Effect="Allow"
                },
            ]
            Version="2012-10-17"
        }
    )
}
"example2_policy" {
    name="example2"
    policy=jsonencode(
        {
            Statement=[
                {
                    Action=[
                        s3:*
                    ]
                    Effect="Allow"
                },
            ]
            Version="2012-10-17"
        }
    )
}
"example3_policy" {
    name="example3"
    policy=jsonencode(
        {
            Statement=[
                {
                    Action=[
                        s3:*
                    ]
                    Effect="Allow"
                },
            ]
            Version="2012-10-17"
        }
    )
}'
perl -0777 -pe 's/^(\h*policy=)[^()]*(\((?:[^()]++|(?2))*\))/<placehlder>/gm' <<< "$s"

输出:

"example1_policy" {
    name="example"
    policy=<placehlder>
}
"example2_policy" {
    name="example2"
    policy=<placehlder>
}
"example3_policy" {
    name="example3"
    policy=<placehlder>
}