【问题标题】:Match a string that contains a newline using sed使用 sed 匹配包含换行符的字符串
【发布时间】:2018-08-20 11:17:54
【问题描述】:

我有一个这样的字符串:

    #
    pap

它基本上转换为\t#\n\tpap,我想将其替换为:

    #
    pap
    python

翻译成\t#\n\tpap\n\tpython

sed 尝试了很多方法,但它不起作用可能是因为sed 以不同的方式使用新行。我试过了:

sed -i "s/\t#\n\tpap/\t#\tpython\n\tpap/" /etc/freeradius/sites-available/default

...以及许多其他没有结果的方法。知道如何在这种情况下进行替换吗?

【问题讨论】:

  • sed 是用于简单替换单行的出色工具。它不适用于涉及跨多行匹配 RE 的任何问题。 1970 年代中期,当 awk 被发明时,sed 语言结构就已经过时了。
  • /^\t#/N;贴在你的前面,它会起作用的。

标签: linux bash ubuntu awk sed


【解决方案1】:

用 gawk 试试这条线:

awk -v RS="\0" -v ORS="" '{gsub(/\t#\n\tpap/,"yourNEwString")}7' file

如果你想让sed处理新行,你必须先阅读整个文件:

sed ':a;N;$!ba;s/\t#\n\tpap/NewString/g' file

【讨论】:

  • 这里是 sed 没有先读取整个文件:sed -e ':b; /^\t#$/ { N; s/\n\tpap$/&\n\tpython/; te; P; D; }; :e'
  • +` 用于 awk 解决方案。我忽略了 sed ,所以我可以 +1 awk :-)。顺便说一句,我最近发现有人解析包含 NUL 字符的文本文件,因此使用 RS='\0' 对他们不起作用,所以我默认使用 RS='^$' 切换到并声明它是 gawk-only。 ^$ 有效,因为这两个字符匹配字符串的开头和结尾,并且 gawk 将输入文件视为要拆分为记录的字符串,因此 RS='^$' 仅适用于空字符串/文件并且不能存在于文件中任何内容。如果他们不能傻眼,那么RS='\0' 是下一个我提到的警告。
  • gsub 结束后的7 是什么?
  • @LironYahdav print
  • 不幸的是,没有解释为什么这会更好。
【解决方案2】:

这可能对你有用(GNU sed):

sed '/^\t#$/{n;/^\tpap$/{p;s//\tpython/}}' file

如果一行只包含 \t# 打印它,那么如果下一行只包含 \tpap 也打印它,然后将该行替换为 \tpython 并打印它。

【讨论】:

  • +1 - 聪明。起初对p 感到困惑,因为n 通常会打印新加载的行,直到我意识到您的s 命令通过在替换字符串中不引用它来有效地删除了该行;换句话说:p;s//\typthon/ 相当于:s//&\n\tpython/
【解决方案3】:

一个 GNU sed 解决方案,不需要一次读取整个文件

sed '/^\t#$/ {n;/^\tpap$/a\\tpython'$'\n''}' file
  • /^\t#$/ 匹配仅注释行(完全匹配 \t#),在这种情况下(仅)执行整个 {...} 表达式:
    • n 加载并打印 next 行。
    • /^\tpap/ 将下一行与 \tpap 完全匹配。
    • 如果匹配,a\\tpython 将在读取以下行之前输出\n\tpython - 请注意,需要拼接换行符 ($'\n') 来表示结束传递给a 命令的文本的数量(您也可以使用多个-e 选项)。

(顺便说一句:使用 BSD sed (OS X),它会变得很麻烦,因为

  • 控制字符。不直接支持 \n\t 等,必须作为 ANSI C 引用的文字进行拼接。
  • 前导空格总是从a 命令的文本参数中去除,因此必须使用替换方法:s//&\'$'\n\t'python'/pap 行替换为自身加上要追加的行:

    sed '/^'$'\t''#$/ {n; /^'$'\t''pap$/ s//&\'$'\n\t'python'/;}' file
    

)


awk 解决方案(符合 POSIX)也不需要一次读取整个文件

awk '{print} /^\t#$/ {f=1;next} f && /^\tpap$/ {print "\tpython"} {f=0}' file
  • {print}: 打印每一行输入
  • /^\t#$/ {f=1;next}:如果找到仅注释行(与 \t# 完全匹配)并移至下一行,则将标志 f(用于“找到”)设置为 1
  • f && /^\tpap$/ {print "\tpython"}:如果一行前面有注释行并且与 \tpap 完全匹配,则输出额外的行 \tpython
  • {f=0}:重置指示仅注释行的标志。

【讨论】:

    【解决方案4】:

    几个纯bash解决方案:

    简洁,但有些脆弱,使用参数扩展:

    in=$'\t#\n\tpap\n' # input string
    
    echo "${in/$'\t#\n\tpap\n'/$'\t#\n\tpap\n\tpython\n'}"
    
    • 参数扩展只支持patterns(通配符表达式)作为搜索字符串,这限制了匹配能力:
    • 这里假设pap 后面跟着\n,而没有假设\t# 前面的内容,可能会导致误报。
    • 如果可以假设\t#\n\tpap 总是封闭\n 中,echo "${in/$'\n\t#\n\tpap\n'/$'\n\t#\n\tpap\n\tpython\n'}" 将有效地工作;否则,请参见下文。

    健壮但冗长,使用=~ 运算符进行正则表达式匹配:

    =~ 运算符支持右侧的扩展正则表达式,因此允许更灵活和健壮的匹配:

    in=$'\t#\n\tpap' # input string 
    
    # Search string and string to append after.
    search=$'\t#\n\tpap'
    append=$'\n\tpython'
    
    out=$in # Initialize output string to input string.
    if [[ $in =~ ^(.*$'\n')?("$search")($'\n'.*)?$ ]]; then # perform regex matching
        out=${out/$search/$search$append} # replace match with match + appendage
    fi
    
    echo "$out"
    

    【讨论】:

      猜你喜欢
      • 2012-06-30
      • 2017-03-20
      • 1970-01-01
      • 2021-12-30
      • 2010-11-12
      • 1970-01-01
      • 2019-04-24
      • 2014-03-30
      相关资源
      最近更新 更多