我想做以下事情
- 使用模式 (
^[ \t]*services:[ \t]*
)搜索文件内容 - 如果找到,则将模式替换为字符串(在变量中)
- 否则,将字符串(在变量中)附加到文件末尾
我陷入了步骤 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"