sed:配置块的多行替换

sed:配置块的多行替换

我有一些配置文件基本上看起来像

(...content...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
(... more content ...)
# END DYNAMIC BLOCK
(... even more content ...)

现在,在 bash 中使用CONTENT=`wget -O - http://$SERVER/get_config.php`,我有了动态块的替代品。

现在如何进行替换以及如何让脚本在文件末尾插入该块(如果不存在)?

答案1

如果你想使用 sed,你可以从命名管道读取。请注意,此代码不会尝试处理错误。如果动态块头多次出现,则脚本将被阻止。

CONTENT_URL="http://$SERVER/get_config.php"
tmp=$(mktemp -d)
(
  cd "$tmp"
  mkfifo dynamic_seen dynamic_content
  : >dynamic_seen & seen_pid=$!
  wget -O dynamic_content "$CONTENT_URL" & wget_pid=$!
  sed -e '/^# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY$/ p' \
      -e '/^# END DYNAMIC BLOCK$/ {'
          -e p -e 'r dynamic_seen' -e 'r dynamic_content' -e '}' \
      -e '/^# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY$/, /^# END DYNAMIC BLOCK$/ d'
  if ! kill $dynamic_seen 2>/dev/null; then
    # The pipe hasn't been read, so there was no dynamic block. Add one.
    echo "# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY"
    cat dynamic_pipe
    echo "# END DYNAMIC BLOCK - DO NOT EDIT MANUALLY"
  fi
)
rm -rf "$tmp"

但我会选择 awk。

export CONTENT_URL="http://$SERVER/get_config.php"
awk '
    $0 == "# END DYNAMIC BLOCK - DO NOT EDIT MANUALLY" {skip=0; system("wget \"$CONTENT_URL\""); substituted=1}
    !skip {print}
    $0 == "# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY" {skip=1}
    END {
         if (!substituted) {
            print "# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY";
            system("wget \"$CONTENT_URL\"");
            print "# END DYNAMIC BLOCK - DO NOT EDIT MANUALLY";
        }
    }
'

答案2

我会使用一个子 shell 和两个 sed 命令,如下所示:

beg_tag='# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY'
end_tag='# END DYNAMIC BLOCK'

(
  sed "/^$beg_tag"'$/,$d' oldconf
  echo "$beg_tag"
  wget -O - http://$SERVER/get_config.php
  echo "$end_tag"
  sed "1,/^$end_tag/d" oldconf
) > newconf

小心不要将任何 sed 重要字符放入beg_tag和中end_tag中。

如果不存在标签,这将附加输出。第一个 sed 命令永远不会从输入中删除任何行,第二个 sed 命令将删除所有行。

测试

如果oldconf包含:

(...content...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
(... more content ...)
# END DYNAMIC BLOCK
(... even more content ...)

并且wget命令替换为echo hello world,输出为:

(...content...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
hello world
# END DYNAMIC BLOCK
(... even more content ...)

现在,如果删除该块,即使用以下输入:

(...content...)
(... even more content ...)

输出是:

(...content...)
(... even more content ...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
hello world
# END DYNAMIC BLOCK

答案3

sed实际上做起来相当简单。您只需要平衡各行之间的范围并锚定到 EOF。

INPUT |
sed -e 's/\\/&&/g;$!s/$/\\/' |        #this sed escapes INPUT for scripting
sed -e '/^'"$START"'/,$!{$!b          #this sed applies concatenated scripts
             G;G;s/$/'"$END"'/;P;:n
};$!N;  /\n'"$END"'/,$!{G;$!bn
};      /\n\n/c\' -f - -e 'P;$d;D
' ./named_infile >outfile

因此,那里发生了一些事情,但其中最重要的是:

/^$START/,$!{ -- function --}
N; /\n$END/,$!{ -- function -- }

这个想法是,当我们行范围为 Line1$我们基本上刚刚完成贪婪的。通常,行范围仅适用于它们可能的行的最小子集 - 每个 LHS 匹配都重新开始,并以输入中下一个出现的第一个 RHS 匹配结束。如果 RHS 是 EOF,那么它们只能应用一次 - 因为只有其中一个。

当我做:

/^$START/,$!{ -- function -- }

我指定花括号之间的所有代码都针对 infile 中的每一行运行,但不是包括$START。在这个函数上下文中,我为不是最后b一行的每一行进行了扩展。!$

通过这种方式,输入中第一行之前的所有行$START都会自动打印并被忽略,但如果$最后一行落在这个范围内 - 因为它可能$START永远不会出现一次 - 那么它就准备好挂c到你的字符串上。

因此,如果您的范围未出现在输入中,则 INPUT 会附加到文件的尾部。

当我下次这样做时:

N; /\n$END/,$!{ -- function -- }

我再次根据上下文应用函数。这次它应用于范围的主体 - 并且仅是它输入的第一次出现 - 因为 的补集是在第一个出现之前未排列的/\n$END/,$所有行,并且仅直到但不包括下一个出现的行。b$START$END

在这种情况下,应用的函数是一个分支循环 - 只要输入落在该范围内,它就会继续b回溯并拉入Next 行,直到找到第一个$END匹配项,此时它将c整个范围挂起到-f -标准输入脚本文件 - 或您的转义输入。如果最后一行发生在第一个$START匹配之前,则同样的规则将应用于最后一行。

就是这样。但请注意,这不需要任何特殊文件即可工作 - 因为它(安全地)包含一份副本输入在其脚本内,不需要r随时将其写入以在需要时应用。

相关内容