这是我的test.env
RABBITMQ_HOST=127.0.0.1
RABBITMQ_PASS=1234
我想用test.sh
将test.env
中的值替换为:
RABBITMQ_HOST=rabbitmq1
RABBITMQ_PASS=12345
这是我的 test.sh
#!/bin/bash
echo "hello world"
RABBITMQ_HOST=rabbitmq1
RABBITMQ_PASS=12345
Deploy_path="./config/test.env"
sed -i 's/RABBITMQ_HOST=.*/RABBITMQ_HOST='$RABBITMQ_HOST'/' $Deploy_path
sed -i 's/RABBITMQ_PASS=.*/RABBITMQ_PASS='$RABBITMQ_HOST'/' $Deploy_path
但我有错误
sed: 1: "./config/test.env": invalid command code .
sed: 1: "./config/test.env": invalid command code .
我该如何解决它?
tl;博士:
使用BSDSed,例如在macOS上也可以找到的,您必须使用-i ''
而不仅仅是-i
(用于不创建备份文件)来使您的命令正常工作;例如:
sed -i '' 's/RABBITMQ_HOST=.*/RABBITMQ_HOST='"$RABBITMQ_HOST"'/' "$Deploy_path"
要使您的命令同时适用于 GNU 和 BSD Sed,请指定一个非空的选项参数(用于创建备份)并将其直接附加到-i
:
sed -i'.bak' 's/RABBITMQ_HOST=.*/RABBITMQ_HOST='"$RABBITMQ_HOST"'/' "$Deploy_path" &&
rm "$Deploy_path.bak" # remove unneeded backup copy
可以在下面找到背景信息、(更多)便携式解决方案和命令的优化。
可选背景信息
听起来您使用的是BSD/macOSsed
,其-i
选项需要一个选项参数,该参数指定要创建的备份文件的后缀。
因此,您的sed
脚本(与您的期望相反)被解释为-i
的选项参数(备份后缀),而您的输入文件名被解释为脚本,这显然失败了。
相比之下,您的命令使用GNUsed
语法,其中-i
可以单独用于指示不保留要就地更新的输入文件的备份文件。
等效的BSDsed
选项是-i ''
- 请注意,技术上需要使用单独的参数来指定选项参数''
,因为它是空字符串(如果你使用-i''
,shell 会在sed
看到它之前简单地剥离''
:-i''
实际上与-i
相同)。
可悲的是,这不适用于GNUsed
,因为它只在直接附加到-i
时识别选项参数,并将单独的''
解释为单独的参数,即脚本。
这种行为差异源于实现-i
选项背后的根本不同的设计决策,并且由于向后兼容性的原因,它可能不会消失。[1]
如果您不希望创建备份文件,则没有适用于 BSD 和 GNUsed
的单一-i
语法。
有四个基本选项:
(a) 如果你知道你只会使用 GNU或BSD
sed
,请相应地构建-i
选项:-i
用于 GNUsed
,-i ''
用于 BSDsed
。(b)指定一个非空后缀作为
-i
的选项参数,如果将其直接附加到-i
选项,则适用于两种实现;例如,-i'.bak'
. 虽然这总是创建一个带有后缀的备份文件.bak
,您可以在之后将其删除。(c) 在运行时确定您正在处理的
sed
实现,并相应地构造-i
选项。(d) 完全省略
-i
(不符合 POSIX 标准),并使用临时文件替换成功时的原始文件:sed '...' "$Deploy_path" > tmp.out && mv tmp.out "$Deploy_path"
。
请注意,这本质上是-i
幕后所做的,这可能会产生意想不到的副作用,特别是作为符号链接的输入文件被常规文件替换; 但是,-i
确实保留了原始文件的某些属性:请参阅我这个答案的下半部分。
下面是 (c) 的一个bash
实现,它还简化了原始代码(带有 2 个替换的单次sed
调用)并使其更加健壮(变量用双引号引起来):
#!/bin/bash
RABBITMQ_HOST='rabbitmq1'
RABBITMQ_PASS='12345'
Deploy_path="test.env"
# Construct the Sed-implementation-specific -i option-argument.
# Caveat: The assumption is that if the `sed` is not GNU Sed, it is BSD Sed,
# but there are Sed implementations that don't support -i at all,
# because, as Steven Penny points out, -i is not part of POSIX.
suffixArg=()
sed --version 2>/dev/null | grep -q GNU || suffixArg=( '' )
sed -i "${suffixArg[@]}" '
s/^(RABBITMQ_HOST)=.*/1='"$RABBITMQ_HOST"'/
s/^(RABBITMQ_PASS)=.*/1='"$RABBITMQ_PASS"'/
' "$Deploy_path"
请注意,使用上面为$RABBITMQ_HOST
和$RABBITMQ_PASS
定义的特定值,将它们直接拼接到sed
脚本中是安全的,但是如果这些值包含&
、/
、或换行符的实例,则需要事先进行转义,以免破坏
sed
命令。
请参阅我的这个答案,了解如何执行通用预转义,但您也可以在这一点上考虑其他工具,例如awk
和perl
。
-i 的选项参数是可选的,而 BSD Sed 认为它是强制性的,这也反映在语法规范中。 在各自的man
页面中:GNU Sed:-i[SUFFIX]
vs. BSD Sed-i extension
。
ex -sc '%!awk "
$1 == "RABBITMQ_HOST" && $2 = "rabbitmq1"
$1 == "RABBITMQ_PASS" && $2 = 12345
" FS== OFS==' -cx file
POSIX Sed 不支持
-i
选项。但是前任可以编辑文件 到位Awk 是一个更好的工具,因为数据被分成记录和 领域
无论是 Sed 还是 Awk,您都可以使用换行符或
;
来完成所有操作 在一次调用中你有双引号的字符串,里面没有变量,不妨使用 单引号
您在文件名没有需要转义的字符时引用了文件名
你有几个未引用的变量用法,几乎从来都不是一个好主意
简单案例
如果test.env
只包含两个变量,您可以简单地创建一个新文件,或覆盖现有的:
printf "RABBITMQ_HOST=%snRABBITMQ_PASS=%sn"
"${RABBITMQ_HOST}" "${RABBITMQ_PASS}" > "$Deploy_path"
修复不带引号的变量并优化 SED 命令
尝试按如下方式修复命令:
sed -i -e 's/(RABBITMQ_HOST=).*/1'"$RABBITMQ_HOST"'/'
-e 's/(RABBITMQ_PASS=).*/1'"$RABBITMQ_PASS"'/'
"$Deploy_path"
您应该将变量括在双引号中,否则 shell 将解释内容。在双引号的内容中,shell 将仅解释$
(将变量替换为其内容)、反引号和(转义)。另请注意使用多个
-e
选项。
为什么 SED 不适合这项任务(在我看来)?
但是,正如@mklement0的回答所说,-i
在BSD系统上可能无法以这种形式工作。此外,如果两个变量$Deploy_path
文件中定义,如果文件存在,则该命令仅修改这两个变量。它不会将新变量添加到文件中。请注意,变量直接嵌入到替换中,它们的值通常应根据 SED 规则进行转义!
另类
如果test.env
文件是可信的,我建议加载变量,修改它们并打印到输出文件:
(
# Load variables from test.env
source test.env
# Override some variables
RABBITMQ_HOST=rabbitmq1
RABBITMQ_PASS=12345
# Print all variables prefixed with "RABBITMQ_".
# In POSIX mode, `set` will not output defines and functions
set -o posix
set | grep ^RABBITMQ_
) > "$Deploy_path"
请考虑调整test.env
的文件系统权限。我想,源文件是一个受信任的模板。
在我看来,没有 SED 的解决方案更好,因为 SED 实现可能会有所不同,并且就地选项可能无法在不同的平台上按预期工作。
但是,source
不是有风险吗?
虽然解析 shell 变量赋值通常是一项简单的任务,但它比仅仅获取现成的"脚本"(test.env) 风险更大。例如,考虑test.env
中的以下行:
declare RABBITMQ_HOST=${MYVAR:=rabbitmq1}
或
export RABBITMQ_HOST=host
除使用source
的代码外,当前建议的所有解决方案都假定您将变量分配为RABBITMQ_HOST=...
。一些解决方案甚至假设RABBIT_HOST
放在行的开头。啊,你可能会修复正则表达式,对吧?就为了这个案子...
因此,source
风险与源文件不受信任一样多。想想 C 中的#include <file>
,或者 PHP 中的include "file.php"
。这些指令还包括当前源中的源。因此,不要盲目地将采购文件视为反模式。这完全取决于具体情况。如果您的test.env是正在部署的存储库的一部分,那么调用source test.env
肯定是安全的。然而,这是我的看法。