匹配文件中的参数并仅替换等于字符后的值

匹配文件中的参数并仅替换等于字符后的值

在以下示例文件中

more ambari-agent.ini
[server]
hostname=AABB

我们想要匹配文件中的单词并仅替换(等于字符)hostname之后的值=

所以我们就这么做了

VAR=server_100

sed -ire "s/(hostname=)[^=]*$/\$VAR/"  /tmp/ambari-agent.ini

但我们仍然有

more ambari-agent.ini
[server]
hostname=AABB

虽然预期结果应该是

more ambari-agent.ini
[server]
hostname=server_100

我们哪里错了?

答案1

VAR=anything
escaped_VAR=$(
  printf '%s\n' "$VAR" |
    LC_ALL=C sed 's|[/&\\]|\\&|g;$!s/$/\\/'
)

LC_ALL=C sed -E -i -e "s/^(hostname=).*/\1$escaped_VAR/" -- "$the_file"

对于 FreeBSD/macOS sed,替换-i-i ''.

对于 GNU (以及最终复制 GNU 而不是 FreeBSD 的 NetBSD/OpenBSD)和 FreeBSD sed-i采用一个参数,即备份扩展的名称。对于 GNU sed,该参数是可选的,并且必须坚持该-i选项,而对于 FreeBSD sed,它是必需的(但传递一个空参数也会禁用保留备份副本)。

-ire对于 GNU 和 FreeBSD 都被解释为-iwith as 参数。在 FreeBSD 中被解释为带参数,而在 GNU 中则被解释为不带参数后跟选项。re-i -e-i-e-i-esed

其他注意事项:

  • GNU 用于-rERE,BSD 用于-E.-E好多了,现在大多数实现都已经解决了,包括 GNU sed。这将由 POSIX 指定。无论如何,这里 ERE 都是不必要的。 ERE 相对于 BRE 的唯一特征是|(交替),其余的只是语法差异。sed -i "s/^\(hostname=\).*/\1$escaped_VAR/"也会同样有效。
  • .*C如果输入包含在除 之外的区域设置中不形成有效字符的字节序列(因此为LC_ALL=C),则可能无法匹配,直到行尾。
  • 如果内容未经净化并且 、 和换行符被转义,则在传递到的代码中扩展 asis 将构成命令注入$VAR漏洞。像您一样使用,只会替换为文字,而不是 shell 变量的内容。sed&\/\$VAR$VAR$VAR
  • 如果值(或者其右侧的任何内容也可能是注释)不包含,则执行操作s/(hostname=)[^=]*$/$escaped_VAR/只会替换该行。我没想到这是故意的。hostnamehostname==
  • 如果没有^,它将匹配hostname=文件中的任何位置。^确保仅完成hostname=在行开头找到的实例。如果您想允许前导空格,您可以随时使用"/^([[:space:]]*hostname...

1 POSIX ERE 也缺少 BRE 的反向引用功能,尽管大多数 ERE 实现现在都支持它们作为扩展

答案2

前面的答案也可以作为一个函数,我只是缺乏声誉来回答那里的评论。

VAR=anything
function escape() { printf '%s\n' "$1" | LC_ALL=C sed 's|[+/&\\]|\\&|g;$!s/$/\\/' }
LC_ALL=C sed -E -e "s/^(hostname=).*/\1$(escape $VAR)/" -i -- "$file"

请注意,我已将+字符修改为转义规则。

这也适用于动态提供VAR或以其他方式提供的替换值。

LC_ALL=C sed -E -e "s/^(> API_SECRET=).*/\1$(escape $(pwgen -n 64 1))/" -i -- diff.patch
VAR="admin"; LC_ALL=C sed -E -e "s/^(+SP_ADMIN_USER=).*/\1$(escape "${VAR}")/" -i diff.patch

该命令与原始示例的不同之处在于省略了可能冗余的-eand --

可以用另一个函数进一步参数化这个逻辑,VAR完全省略。

function replace() { LC_ALL=C sed -E -e "s/^($(escape "${1}")).*/\1$(escape "${2}")/" -i "${3}" }
replace "+ADMIN_USER=" "admin" diff.patch

然后可以以编程方式使用它来替换所选行开头之后的值。

相关内容