使用sed替换bash中的参数

时间:2017-11-11 20:11:31

标签: bash sed

尝试清理几十个冗余的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:找不到事件

由于

3 个答案:

答案 0 :(得分:2)

免责声明:这个问题在信息方面有点亮,有点像“为我编写代码”。善意地我假设它不是那样,所以我希望这可以用来更多地了解文本处理/正则表达式替换,而不仅仅是在某处复制粘贴而忘记。

我建议使用perl代替sed。虽然sed通常是工作的正确工具,但在这种情况下,我认为Perl更好,原因如下:

  • Perl让您可以轻松地在正则表达式上进行多行匹配。这可以使用sed,但很难(有关详细信息,请参阅this question)。
  • 对于多行和复杂的分隔符和引号字符,sed开始显示不同的行为,具体取决于您使用它的平台。例如,尝试在“sorta multiline”模式下使用sed执行此操作会在OSX与Linux上实现不同的结果(实际上是GNU sed vs BSD sed)。当使用像这样的半高级功能时,我会坚持使用一种在不同平台上表现一致的工具,在这种情况下Perl会这样做。
  • Perl允许您处理ASCII值和其他特殊字符,而无需大量的“牙签塔”逃逸或替代。由于使用ASCII值来匹配模式中的单引号很方便(我们可以使用混合的双引号和单引号,但这使得将此命令复制/粘贴到子shell或eval中变得更加困难作为脚本的一部分,最好使用支持它的工具而不会有额外的麻烦。这可能是sed,但很棘手;有关详细信息,请参阅this article
  • sed / BRE中,做一些像“一个或多个”匹配这样简单的事情通常需要转义特殊字符,即[[:space:]]\{1,\},这会变得单调乏味。由于在此模式中使用大量重复/分组字符很方便,因此在这种情况下我更喜欢Perl的简洁性,因为它提高了匹配代码的清晰度。
  • 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