尝试清理几十个冗余的nagios配置文件,但是sed对我不起作用(是的,我对bash来说相当新),这里是我要替换的字符串:< / p>
use store-service
host_name myhost
service_description HTTP_JVM_SYM_DS
check_command check_http!'-p 8080 -N -u /SymmetricDS/app'
check_interval 1
用这个:
use my-template-service
host_name myhost
只是 host_name应该保持不变,因为每个文件都不同。任何帮助将不胜感激。试图逃离&#39;和!,但收到此错误 -bash:!&#39; -p:找不到事件
由于
答案 0 :(得分:2)
免责声明:这个问题在信息方面有点亮,有点像“为我编写代码”。善意地我假设它不是那样,所以我希望这可以用来更多地了解文本处理/正则表达式替换,而不仅仅是在某处复制粘贴而忘记。
我建议使用perl
代替sed
。虽然sed
通常是工作的正确工具,但在这种情况下,我认为Perl更好,原因如下:
sed
,但很难(有关详细信息,请参阅this question)。 sed
开始显示不同的行为,具体取决于您使用它的平台。例如,尝试在“sorta multiline”模式下使用sed
执行此操作会在OSX与Linux上实现不同的结果(实际上是GNU sed
vs BSD sed
)。当使用像这样的半高级功能时,我会坚持使用一种在不同平台上表现一致的工具,在这种情况下Perl会这样做。eval
中变得更加困难作为脚本的一部分,最好使用支持它的工具而不会有额外的麻烦。这可能是sed
,但很棘手;有关详细信息,请参阅this article。sed
/ BRE中,做一些像“一个或多个”匹配这样简单的事情通常需要转义特殊字符,即[[:space:]]\{1,\}
,这会变得单调乏味。由于在此模式中使用大量重复/分组字符很方便,因此在这种情况下我更喜欢Perl的简洁性,因为它提高了匹配代码的清晰度。x
修饰符以单行模式在正则表达式语句中编写注释。对于像这样的大型多线模式,如果您需要返回并更改它,将模式分解并注释以便于阅读确实有帮助。 sed
has comments too,但在单一可粘贴命令模式下使用它们(而不是sed
脚本代码的文件)可能会很棘手,并且可能导致命令的可读性降低。无论如何,以下是我想出的匹配器。它尽可能地内联注释,但是这里解释了未注释的部分:
-0777
开关告诉perl在处理输入文件之前使用整个输入文件,而不是逐行操作。有关此标志和其他标志的详细信息,请参阅perlrun。感谢@glennjackman在原始问题的评论中指出这一点!-p
开关告诉Perl读取STDIN,直到它看到一个分隔符(由-0777
设置的输入结束),运行提供的程序,然后打印该程序的返回值关闭。由于我们的“程序”只是一个字符串替换语句,因此它的返回值是替换字符串。-e
开关告诉perl评估要运行的程序的下一个字符串参数,而不是查找脚本文件或类似文件。mytext.txt
,可能是包含您的模式的文件。您也可以将输入传输到Perl,例如通过cat mytext.txt | perl ...
,它将以完全相同的方式工作。m
修饰符来匹配多个\ n分隔语句和扩展x
修饰符,以便我们可以有注释并关闭匹配文字空白,为清楚起见。如果你愿意的话,你可以摆脱评论和文字空白并将它们全部拼凑成一行,但是在忘记它的作用之后,祝你好运。有关这些修饰符的详细信息,请参阅perlre。此命令将在您包含它的文件中替换您提供的 literal 字符串(它可能不仅仅包含该字符串之前/之后;只会处理该文本块)。它以一种次要的方式小于文字:它允许每行中第一个和第二个单词之间的任何数字(一个或多个)空格字符。如果我记得Nagios的配置,那么空格的数量无论如何都不是特别重要。
此命令将不更改其提供的文件的内容。如果文件与模式不匹配,则此命令将不会更改其内容。如果它包含该模式,则将打印出替换的内容。您可以将这些内容写入新文件,或者随意做任何事情。
perl -0777pe '
# Use the pipe "|" character as an expression delimiter, since
# the pattern contains slashes.
s|
# 'use', one or more space-equivalent characters, and then 'store-service',
# on one line.
use \s+ store-service \n
# Open a capturing group.
(
# Capture the host name line in its entirety, then close the group.
host_name \s+ \S+
# Close the group and end the line.
) \n
service_description \s+ HTTP_JVM_SYM_DS \n
# Look for check_command, spaces, and check_http!, but keep matching on the
# same line.
check_command \s+ check_http!
# Look for a single quote character by ASCII value, since shell
# escaping these can be ugly/tricky, and makes your code less copy-
# pasteable in/out of scripts/subcommands.
\047
# Look for the arguments to check_http, delimited by explicit \s
# spaces, since we are in "extended" mode in order to be able to write
# these comments and the expression on multiple lines.
-p \s 8080 \s -N \s -u \s /SymmetricDS/app
# Look for another single quote and the end of the line.
\047 \n
check_interval \s+ 1\n
# Replace all of the matched text with the "use my-template-service" line,
# followed by the contents of the first matching group (the host_name line).
# You could capture the "use" statement in another group, or use e.g.
# sprintf() to align fields here instead of a big literal space line, but
# this is the simplest, most obvious way to get the replacement done.
|use my-template-service\n$1|mx
' < mytext.txt
答案 1 :(得分:1)
假设您可以对要感兴趣的日志文件中的文件进行选通,我首先会将要替换的文件过滤为限制为五行。
你可以用Bash和awk做到这一点:
for fn in *; do # make that glob apply to your files...
[[ -e "$fn" && -f "$fn" && -s "$fn" ]] || continue
line_cnt=$(awk 'FNR==NR{next}
END {print NR}' "$fn")
(( line_cnt == 5 )) || continue
# at this point you only have files with 5 lines of text...
done
完成后,您可以在循环中添加另一个awk
来进行替换:
for fn in *; do
[[ -e "$fn" && -f "$fn" && -s "$fn" ]] || continue
line_cnt=$(awk -v l=5 'FNR==NR{next}
END {print NR}' "$fn")
(( line_cnt == 5 )) || continue
awk 'BEGIN{tgt["use"]="my-template-service"
tgt["host_name"]=""}
$1 in tgt { if (tgt[$1]=="") s=$2
else s=tgt[$1]
printf "%-33s%s\n", $1, s
}
' "$fn"
done
答案 2 :(得分:0)
这是 GNU sed 解决方案,请检查一下。在测试之前备份您的文件。
#!/bin/bash
# You should escape all special characters in this string (like $, ^, /, {, }, etc),
# which you need interpreted literally, not as regex - by the backslash.
# Your original string was contained only slashes from this list, but
# I decide don't escape them by backslashes, but change sed's s/pattern/replace/
# command to the s|patter|replace|. You can pick any more fittable character.
needle="use\s{1,}store-service\n\
host_name\s{1,}myhost\n\
service_description\s{1,}HTTP_JVM_SYM_DS\n\
check_command\s{1,}check_http!'-p 8080 -N -u /SymmetricDS/app'\n\
check_interval\s{1,}1"
replacement="use my-template-service\n\
host_name myhost"
# This echo command displays the generated substitute command,
# which will be used by sed
# uncomment it for viewing
# echo "s/$needle/$replacement/"
# for changing the file in place add the -i option.
sed -r "
/use\s{1,}store-service/ {
N;N;N;N;
s|$needle|$replacement|
}" input.txt
<强>输入强>
one
two
use store-service
host_name myhost
service_description HTTP_JVM_SYM_DS
check_command check_http!'-p 8080 -N -u /SymmetricDS/app'
check_interval 1
three
four
<强>输出强>
one
two
use my-template-service
host_name myhost
three
four