shell script replace variables in file - error with Sed's -i option for in-place updating
这是我的
1 2 | RABBITMQ_HOST=127.0.0.1 RABBITMQ_PASS=1234 |
我想使用
1 2 | RABBITMQ_HOST=rabbitmq1 RABBITMQ_PASS=12345 |
这是我的test.sh
1 2 3 4 5 6 7 8 9 | #!/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 |
但是我有错误
1 2 | sed: 1:"./config/test.env": invalid command code . sed: 1:"./config/test.env": invalid command code . |
我该如何解决?
tl; dr:
使用BSD Sed(例如在macOS上也可以找到),必须使用
1 | sed -i '' 's/RABBITMQ_HOST=.*/RABBITMQ_HOST='"$RABBITMQ_HOST"'/' "$Deploy_path" |
要使您的命令与GNU和BSD Sed一起使用,请指定一个非空选项参数(创建备份)并将其直接附加到
1 2 | sed -i'.bak' 's/RABBITMQ_HOST=.*/RABBITMQ_HOST='"$RABBITMQ_HOST"'/' "$Deploy_path" && rm"$Deploy_path.bak" # remove unneeded backup copy |
背景信息,(更多)便携式解决方案和命令的改进可以在下面找到。
可选背景信息
听起来好像您正在使用BSD / macOS
因此,是您的
相比之下,您的命令使用GNU
等效的BSD
遗憾的是,这将不适用于GNU
这种行为上的差异源于实现
如果您不希望创建备份文件,则没有适用于BSD和GNU
有四个基本选项:
-
(a)如果您只使用GNU或BSD
sed ,则相应地构造-i 选项:对于GNUsed ,-i ,对于BSDsed ,-i '' 。 -
(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)的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #!/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" |
请注意,使用上面为
有关如何执行通用预转义的信息,请参见我的答案,但是此时您还可以考虑使用其他工具,例如
[1] GNU Sed认为-i的选项参数是可选的,而BSD Sed则认为它是强制性的,这也反映在语法规范中。在相应的
1 2 3 4 | ex -sc '%!awk"\\ \\$1 == "RABBITMQ_HOST" && \\$2 = "rabbitmq1"\\ \\$1 == "RABBITMQ_PASS" && \\$2 = 12345\\ " FS== OFS==' -cx file |
POSIX Sed不支持
就位
Awk是一个更好的工具,因为数据被分为记录和
字段
无论是Sed还是Awk,您都可以使用换行符或
一次调用
您在双引号中没有任何变量的字符串,不妨使用
单引号
当文件名中没有需要转义的字符时,您引用了文件名
您在变量中使用了几种未加引号的用法,几乎从来都不是一个好主意
简单案例
如果
1 2 3 4 | printf"RABBITMQ_HOST=%s\ RABBITMQ_PASS=%s\ " \\ "${RABBITMQ_HOST}""${RABBITMQ_PASS}">"$Deploy_path" |
修复未引用的变量并优化SED命令
尝试如下修复您的命令:
1 2 3 | sed -i -e 's/\\(RABBITMQ_HOST=\\).*/\\1'"$RABBITMQ_HOST"'/' \\ -e 's/\\(RABBITMQ_PASS=\\).*/\\1'"$RABBITMQ_PASS"'/' \\ "$Deploy_path" |
您应该将变量用双引号引起来,否则shell将解释其内容。在双引号中的内容中,shell将仅解释
为什么SED对此任务不利(我认为)?
但是,正如@ mklement0的回答所说,
替代
如果
1 2 3 4 5 6 7 8 9 10 11 12 13 | ( # 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" |
考虑调整
在我看来,没有SED的解决方案更好,因为SED的实现可能会有所不同,并且就地选项可能无法在不同平台上正常工作。
但是,
虽然解析shell变量分配通常是一件容易的事,但与仅提供现成的"脚本"(test.env)相比,它具有更大的风险。例如,考虑
1 | declare RABBITMQ_HOST=${MYVAR:=rabbitmq1} |
或
1 | export RABBITMQ_HOST=host |
所有当前建议的解决方案(使用
因此,