Linux 使用sed匹配包含换行符的字符串

Linux 使用sed匹配包含换行符的字符串,linux,bash,ubuntu,awk,sed,Linux,Bash,Ubuntu,Awk,Sed,我有一根像这样的线: # 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 …还有许多其他方法都没有结果。你知道

我有一根像这样的线:

    #
    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

…还有许多其他方法都没有结果。你知道在这种情况下我该如何替换吗?

用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

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

  • /^\t#$/
    只匹配注释行(精确匹配
    \t#
    ),在这种情况下(仅)执行整个
    {…}
    表达式:
    • n
      加载并打印下一行
    • /^\tpap/
      将下一行与
      \tpap
      精确匹配
    • 如果匹配,
      a\\tpython
      将在读取下一行之前输出
      \n\tpython
      -请注意,拼接的换行符(
      $'\n'
      )需要表示传递给
      a
      命令的文本结束(您也可以使用多个
      -e
      选项)
(顺便提一下:使用BSD
sed
(OSX),它会变得很麻烦,因为

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

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


一种不需要一次读取整个文件的
awk
解决方案(兼容POSIX):

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

两个纯
bash
解决方案:

简洁,但有些脆弱,使用参数展开:
  • 参数扩展仅支持模式(通配符表达式)作为搜索字符串,这限制了匹配能力:
  • 这里假设
    pap
    后面跟着
    \n
    ,而没有假设
    \t#
    前面是什么,可能会导致误报
  • 如果可以假设
    \t\n\tpap
    总是包含在
    \n
    中,
    回音“${in/$”\n\t\tpap\n'/$”\n\t\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"
这可能适用于您(GNU-sed):


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

这里是sed,没有先读取整个文件:
sed-e':b/^\t#$/{N;s/\N\tpap$/&\N\tpython/;te;P;D;};:e'
+`用于awk解决方案。我忽略了sed一个,所以我可以+1 awk一个:-)。顺便说一句,我最近发现有人解析包含
NUL
字符的文本文件,因此使用
RS='\0'
对他们不起作用,所以我默认使用
RS='^$'
切换到使用
^$
之所以有效,是因为这两个字符匹配字符串的开头和结尾,而gawk将输入文件视为要拆分为记录的字符串,因此
RS='^$'
仅对空字符串/文件有效,不能存在于包含任何内容的文件中。如果他们不能呆呆地看,那么接下来就是我提到的警告。
RS='\0'
。在
gsub
结束后,
7
是做什么的?@LironYahdav
print
不幸的是,没有解释为什么这样效果更好。sed是一个很好的工具,可以在一行中进行简单的替换。它不适用于涉及跨多行匹配REs的任何问题。20世纪70年代中期,当awk被发明时,sed语言的结构已经过时在你的前面,它会起作用。+1-聪明。起初对
p
感到困惑,因为
n
通常会打印新加载的行,直到我意识到您的
s
命令通过在替换字符串中不引用该行来有效地删除该行;换句话说:
p;s//\typthon/
相当于:
s/&\n\tpython/
awk '{print} /^\t#$/ {f=1;next} f && /^\tpap$/ {print "\tpython"} {f=0}' file
in=$'\t#\n\tpap\n' # input string

echo "${in/$'\t#\n\tpap\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"
sed '/^\t#$/{n;/^\tpap$/{p;s//\tpython/}}' file