Regex 替换给定搜索模式N行以上的部分行

Regex 替换给定搜索模式N行以上的部分行,regex,bash,replace,awk,Regex,Bash,Replace,Awk,我有一个包含我的Nagios主机的文件,其中我需要更改一些主机的父母,我对如何用脚本解决这个问题一无所知 该文件位于freebsd服务器上,我使用bash作为首选shell 文件的结构如下所示: define host{ use template-here name testname alias testalias check_command check-host-ali

我有一个包含我的Nagios主机的文件,其中我需要更改一些主机的父母,我对如何用脚本解决这个问题一无所知

该文件位于freebsd服务器上,我使用bash作为首选shell

文件的结构如下所示:

define host{
use                    template-here
name                   testname
alias                  testalias
check_command          check-host-alive
max_check_attempts     5
contact_groups         group1
parents                parent1.abc.local   //2. Change this line
notification_interval  240
notification_period    none
notification_options   d,r
register               0
}

define host{
host_name              host2234.abc.local  //1. Search for this line
address                10.10.10.5
notification_period    07-21
use                    testname
}
grep -B10 -m1 2234.abc stack
max_check_attempts     5
contact_groups         group1
parents                parent1.abc.local
notification_interval  240
notification_period    none
notification_options   d,r
register               0
}

define host{
host_name              host2234.abc.local
我唯一能100%确定并搜索文件的模式是主机名行,它总是包含4个数字

如果我做一个grep,grep-B10-m12234.abc文件,我可以100%确定我会得到结果中包含的我正在搜索的主机的正确父级,而这就是我被困的地方。 如何继续更改grep结果中的父项,然后将父项的新值保存到文件中。将所有这些放在一个循环中,因为我需要在多个主机上更改父主机

上面实际grep的输出如下所示:

define host{
use                    template-here
name                   testname
alias                  testalias
check_command          check-host-alive
max_check_attempts     5
contact_groups         group1
parents                parent1.abc.local   //2. Change this line
notification_interval  240
notification_period    none
notification_options   d,r
register               0
}

define host{
host_name              host2234.abc.local  //1. Search for this line
address                10.10.10.5
notification_period    07-21
use                    testname
}
grep -B10 -m1 2234.abc stack
max_check_attempts     5
contact_groups         group1
parents                parent1.abc.local
notification_interval  240
notification_period    none
notification_options   d,r
register               0
}

define host{
host_name              host2234.abc.local
同一文件中另一个主机的第二个示例,其中包含其他行,并且具有与第一个示例相同的要更改的父级

grep -B10 -m1 2235.abc stack
contact_groups         group2
parents                parent1.abc.local
notification_interval  240
notification_period    none
notification_options   d,r
register               0
active_checks_enabled  1
}

define host{
host_name              host2235.abc.local
为了避免任何混淆,我不可能在整个文件中替换父行上的整个字符串,因为我不会移动连接到当前父行的所有主机

我有几个其他类似的用例,其中同样的逻辑适用,所以如果有人能给我一个正确的方向,让轮子旋转,而不是仅仅发布一个完整的解决方案,我会非常高兴。 嗯,除非这是我在搜索和谷歌搜索时找不到的线程的副本

谢谢

使用Ed Mortons脚本的结果进行更新,它只打印文件的内容而不更改行:

root@workdawg:script # cat tst.awk 
BEGIN { RS=""; ORS="\n\n" }
/2234\.abc/ { sub(/parents[^\n]+/,"parents\t\t\t*** Eureka! ***",prev) }
NR>1 { print prev }
{ prev = $0 }
END { print prev }
root@workdawg:script # awk -f tst.awk  stack
define host{
use                    template-here
name                   testname
alias                  testalias
check_command          check-host-alive
max_check_attempts     5
contact_groups         group1
parents                parent3.abc.local
notification_interval  240
notification_period    none
notification_options   d,r
register               0
}

define host{
host_name              host2234.abc.local
address                10.10.10.5
notification_period    07-21
use                    testname
}

有很多方法可以做到这一点。这个例子使用一个带有grep-n和sed的bash脚本来完成这项工作:

#!/bin/sh
file="whatever.conf"
# Get the line numbers from the text
find_grep_line_number() {
  local line_txt=$1
  local x
  i=0

  # Isolate the line number from grep -n before the :
  local REG_EX="^(.*):"
  if [[ $line_txt =~ $REG_EX ]]
  then
    local n=${#BASH_REMATCH[*]}
    for (( x=0; x<$n; x++ ));
    do
      #echo "$x: ${BASH_REMATCH[$x]}"
      i=${BASH_REMATCH[$x]}
    done
  fi
}

line_host_txt=`grep -n "^host_name" $file`
find_grep_line_number $line_host_txt
host_line=$i
line_parents_txt=`grep -n "^parents" $file`
find_grep_line_number $line_parents_txt
parents_line=$i

replacement_parents_line="parents                parent1.abc.local"
# Replace the parents line with a new line.
rslts=`sed -i "$parents_line c\$replacement_parents_line" $file`

我没有测试这个脚本,但它应该可以工作LoL。。sed非常适合做这类事情

您没有显示预期的输出,所以这只是一个猜测,但这是您想要的吗

$ cat tst.awk
BEGIN { RS=""; ORS="\n\n" }
/2234\.abc/ { sub(/parents[^\n]+/,"parents\t\t\t*** Eureka! ***",prev) }
NR>1 { print prev }
{ prev = $0 }
END { print prev }

$ awk -f tst.awk file
define host{
use                    template-here
name                   testname
alias                  testalias
check_command          check-host-alive
max_check_attempts     5
contact_groups         group1
parents                 *** Eureka! ***
notification_interval  240
notification_period    none
notification_options   d,r
register               0
}

define host{
host_name              host2234.abc.local  //1. Search for this line
address                10.10.10.5
notification_period    07-21
use                    testname
}

上述方法每次读取一个空白行分隔的文本块,并始终在最近一次读取之前打印记录。如果当前记录包含2234.abc,则子记录会在打印前替换上一条记录中的父行。

每个块之间是否始终有换行符?通常是,但不总是。某些主机可能还有额外的检查,但搜索主机名上方的10行将始终得到父行。请提供一个示例输出,这样可以更轻松地编写脚本。-最好是第二个例子,其中情况有所不同。如果我理解正确,我已经在原始问题中添加了输出和第二个例子。sed对于这样的作业来说绝对是错误的工具,这就是为什么您需要一个相对较大的shell脚本来尝试支持它,并且最终结果非常脆弱。awk是100%适合它的工具,但它没有贴上awk标签,所以我怀疑是否有很多awk专家会关注它,我感到很懒。shell只是对工具的调用进行排序,sed是在一行上进行简单替换的唯一正确工具,对于其他任何内容,您都应该使用UNIX通用文本处理工具-awk.GoinOff,谢谢您的回答。此选项部分有效,无论我搜索文件的内容是什么,它都会更改文件中包含父项的第一行,而不是连接到我搜索的主机的父项。我修复了grep行以查找以父项开头的行,而不是全局行搜索^父项和^host\u名称是新的。试一试,看看这是否有帮助。你做对了!这正是我想做的。但是,这对我来说不起作用,也不适用于我的实际配置或下面的示例。我的awk版本是4.1.1.Ed Morton,我已经用你的脚本输出更新了我的原始问题。很久以后,我终于回来标记你的答案为解决方案。我必须清理整个配置文件,因为它是一个混乱的空间,到处都是标签和换行符。