用变量替换文件内容中匹配的正则表达式

用变量替换文件内容中匹配的正则表达式

我想做以下事情

  1. 使用模式 ( ^[ \t]*services:[ \t]*)搜索文件内容
  2. 如果找到,则将模式替换为字符串(在变量中)
  3. 否则,将字符串(在变量中)附加到文件末尾

我陷入了步骤 2,脚本的步骤 2 将services:在文件中替换为${serviceLoopBack}.我的命令有什么问题sed

测试脚本

#!/bin/bash
set -e

destfile=config.yml

serviceLoopBack="services:|NL|- name: loopback-service|NL||FS||FS|url: http://127.0.0.1:12345|NL||FS||FS|routes:|NL||FS||FS|- name: loopback-test-route"
serviceLoopBackWithFrontSpace="|NL||NL|$serviceLoopBack"

if grep -E "^[ \t]*services:[ \t]*" $destfile
then
    # service tag found, replace target string
    sed -i -e 's/^[ \t]*services:[ \t]*/${serviceLoopBack}/g' $destfile
else
    # service tag not found, append at end of file
    echo $serviceLoopBackWithFrontSpace >> $destfile
fi

# replace separator 1 to new line
sed  -i -e 's/|NL|/\n/g' $destfile

# replace separator 2 to space
sed  -i -e 's/|FS|/ /g' $destfile

答案1

问题是在第二次sed调用中,您将程序用单引号 ( ' ... ') 括起来。虽然这是推荐的做法,但单引号会阻止 shell 扩展 shell 变量,例如${serviceLoopBack},因此文本将逐字保留,而不是被 shell 变量的内容替换。

一种可能性是使用双引号而不是单引号,如

sed -i -e "s,^[ \t]*services:[ \t]*,${serviceLoopBack}," "$destfile"

或者,sed使用以下命令将表达式汇编到 shell 变量中printf

printf -v prog 's,^[ \t]*services:[ \t]*,%s,' "$serviceLoopBack"
sed -i -e "$prog" "$destfile"

笔记我使用它,作为分隔符而不是/,因为您的替换文本/也包含 ,否则会造成混淆sed。另外,我省略了该g选项,因为我假设每行只有一个匹配项。

无论如何,您可能想查看 shell 脚本中的引用,请参阅例如这个答案论原因。

但是,如果sed不是严格要求,几乎整个 shell 脚本都可以用程序替换awk(这样更快):

awk -v slb="services:|NL|- name: loopback-service|NL||FS||FS|url: http://127.0.0.1:12345|NL||FS||FS|routes:|NL||FS||FS|- name: loopback-test-route" \
 '/^[ \t]*services:/{$0=slb;f=1}
  {gsub(/\|NL\|/,"\n"); gsub(/\|FS\|/," ")} 1; END{if (!f) {print "\n\n" slb}}' "$destfile"
  • awk这将在变量中导入“服务环回”规范slb
  • 然后它会检查是否有任何行与services:模式匹配,如果是,则用存储的字符串替换整行slb(我推断这是您真正想要实现的)。同时,设置一个flagf为1。
  • 使用gsub(),将所有出现的 替换|NL|为换行符,并将所有出现的 替换|FS|为空格。
  • 看似“杂乱”的1指令指示awk打印当前行,包括迄今为止所做的所有修改。
  • 最后,如果f仍未设置,请在“服务环回”行中附加前导模式(但是,已转换为\n\n)。

请注意,除非您有理解该选项的实现awk,否则不会就地编辑文件。您可能必须将输出重定向到临时文件,然后用临时文件替换。awk-i inplace$destfile

然而,总而言之,由于您使用的是结构化语言 (YAML),因此使用专用解析器(例如yq代替面向行的文本处理工具)可能是值得的。

答案2

您的命令的问题sed是变量不会在单引号内扩展,因此您需要将 sed 脚本括在双引号中。为了显示:

$ var="foo"
$ echo bar | sed 's/bar/$var/'
$var

$ echo bar | sed "s/bar/$var/"
foo

您的下一个问题将是您的替换字符串包含,/因此您需要使用不同的分隔符。就像是:

sed -i -e "s#^[ \t]*services:[ \t]*#${serviceLoopBack}#g" "$destfile"

有什么意义吗g?您期望每行有不止一场匹配吗?

以下是脚本的工作版本,进行了一些改进(引用、用于grep -qm1删除输出并在第一个匹配处停止):

#!/bin/bash
set -e

destfile=config.yml

serviceLoopBack="services:|NL|- name: loopback-service|NL||FS||FS|url: http://127.0.0.1:12345|NL||FS||FS|routes:|NL||FS||FS|- name: loopback-test-route"
serviceLoopBackWithFrontSpace="|NL||NL|$serviceLoopBack"

if grep -Eqm 1 "^[ \t]*services:[ \t]*" "$destfile"
then
    # service tag found, replace target string
    sed -i -e "s#^[ \t]*services:[ \t]*#${serviceLoopBack}#g" "$destfile"
else
    # service tag not found, append at end of file
    printf '%s\n' "$serviceLoopBackWithFrontSpace" >> "$destfile"
fi

# replace separator 1 with newline and separator 2 with space
sed -i -e 's/|NL|/\n/g' -e 's/|FS|/ /g'  "$destfile"

相关内容