shell 脚本替换文件中的变量 - Sed 的 -i 选项用于就地更新时出错 [英] shell script replace variables in file - error with Sed's -i option for in-place updating
问题描述
这是我的test.env
RABBITMQ_HOST=127.0.0.1
RABBITMQ_PASS=1234
我想使用 test.sh
将 test.env
中的值替换为:
And I want to use test.sh
to replace the value in test.env
to :
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;dr:
使用 BSD Sed,例如在 macOS 上也可以找到,您必须使用 -i ''
code> 而不是 -i
(用于不创建备份文件)使您的命令工作;例如:
With BSD Sed, such as also found on macOS, you must use -i ''
instead of just -i
(for not creating a backup file) to make your commands work; e.g.:
sed -i '' 's/RABBITMQ_HOST=.*/RABBITMQ_HOST='"$RABBITMQ_HOST"'/' "$Deploy_path"
要使您的命令同时适用于GNU 和 BSD Sed,请指定一个非空选项参数(创建备份)并将其直接附加到-i
:
To make your command work with both GNU and BSD Sed, specify a nonempty option-argument (which creates a backup) and attach it directly to -i
:
sed -i'.bak' 's/RABBITMQ_HOST=.*/RABBITMQ_HOST='"$RABBITMQ_HOST"'/' "$Deploy_path" &&
rm "$Deploy_path.bak" # remove unneeded backup copy
可以在下面找到背景信息、(更多)便携式解决方案和改进的命令.
Background information, (more) portable solutions, and refinement of your commands can be found below.
听起来您正在使用 BSD/macOS sed
,其 -i
选项 需要 一个选项-argument 指定要创建的备份文件的后缀.
因此,您的 sed
script(与您的预期相反)被解释为 -i
的 option-argument(备份后缀),并且您输入的文件名被解释为脚本,这显然失败了.
It sounds like you're using BSD/macOS sed
, whose -i
option requires an option-argument that specifies the suffix of the backup file to create.
Therefore, it is your sed
script that (against your expectations) is interpreted as -i
's option-argument (the backup suffix), and your input filename is interpreted as the script, which obviously fails.
相比之下,您的命令使用 GNU sed
语法,其中 -i
可以单独用于指示 no 输入文件的备份文件到位更新.
By contrast, your commands use GNU sed
syntax, where -i
can be used by itself to indicate that no backup file of the input file to updated in-place is to be kept.
等效的 BSD sed
选项是 -i ''
- 注意技术需要使用 separate 参数来指定选项参数 ''
,因为它是空字符串(如果你使用了 -i''
,shell 会简单地在 sed
看到它之前去掉 ''
:-i''
实际上与 -i
相同).
The equivalent BSD sed
option is -i ''
- note the technical need to use a separate argument to specify the option-argument ''
, because it is the empty string (if you used -i''
, the shell would simply strip the ''
before sed
ever sees it: -i''
is effectively the same as just -i
).
遗憾的是,这不适用于 GNU sed
,因为它仅在 直接附加到 时识别选项参数>-i
,并将单独的 ''
解释为单独的参数,即 script.
Sadly, this then won't work with GNU sed
, because it only recognizes the option-argument when directly attached to -i
, and would interpret the separate ''
as a separate argument, namely as the script.
这种行为差异源于实施 -i
选项背后的根本不同的设计决策,并且出于向后兼容性的原因,它可能不会消失.[1]
This difference in behavior stems from a fundamentally differing design decision behind the implementation of the -i
option and it probably won't go away for reasons of backward compatibility.[1]
如果您不想创建备份文件,则没有单一的 -i
语法适用于 BSD 和GNU sed
.
If you do not want a backup file created, there is no single -i
syntax that works for both BSD and GNU sed
.
有四个基本选项:
(a) 如果您知道您将只使用 GNU 或 BSD
sed
,请构建-i
相应的选项:-i
用于 GNUsed
,-i ''
用于 BSDsed
>.
(a) If you know that you'll only be using either GNU or BSD
sed
, construct the-i
option accordingly:-i
for GNUsed
,-i ''
for BSDsed
.
(b) 指定一个非空后缀作为-i
的选项参数,如果你直接附加它到 -i
选项,适用于 both 实现;例如,-i'.bak'
.虽然这总是会创建一个后缀为 .bak
的备份文件,但您可以在之后删除它.
(b) Specify a nonempty suffix as -i
's option-argument, which, if you attach it directly to the -i
option, works with both implementations; e.g., -i'.bak'
. While this invariably creates a backup file with suffix .bak
, you can just delete it afterward.
(c) 在运行时确定您正在处理的 sed
实现并相应地构造 -i
选项.
(c) Determine at runtime which sed
implementation you're dealing with and construct the -i
option accordingly.
(d) 完全省略 -i
(不符合 POSIX 标准),并使用一个临时文件在成功时替换原始文件: sed '...'"$Deploy_path" >tmp.out &&mv tmp.out "$Deploy_path"
.
请注意,这本质上是 -i
在幕后所做的,这可能会产生意想不到的副作用,特别是输入文件是 symlink 被替换为 常规文件;-i
确实会保留原始文件的某些属性:请参阅此答案的下半部分 我的.
(d) omit -i
(which is not POSIX-compliant) altogether, and use a temporary file that replaces the original on success: sed '...' "$Deploy_path" > tmp.out && mv tmp.out "$Deploy_path"
.
Note that this is in essence what -i
does behind the scenes, which can have unexpected side effects, notably an input file that is a symlink getting replaced with a regular file; -i
, does, however, preserve certain attributes of the original file: see the lower half of this answer of mine.
这是 (c) 的 bash
实现,它也简化了原始代码(带有 2 个替换的单个 sed
调用)并使其更健壮(变量用双引号引起来)):
Here's a bash
implementation of (c) that also streamlines the original code (single sed
invocation with 2 substitutions) and makes it more robust (variables are double-quoted):
#!/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
.
Note that with the specific values defined above for $RABBITMQ_HOST
and $RABBITMQ_PASS
, it is safe to splice them directly into the sed
script, but if the values contained instances of &
, /
, , or newlines, prior escaping would be required so as not to break the
sed
command.
See this answer of mine for how to perform generic pre-escaping, but you may also consider other tools at that point, such as awk
and perl
.
[1] GNU Sed 认为 option-argument 为 -i optional,而 BSD Sed 认为它mandatory,这也反映在语法规范中.在各自的 man
页面中:GNU Sed:-i[SUFFIX]
vs. BSD Sed -i extension
.
[1] GNU Sed considers the option-argument to -i optional, whereas BSD Sed considers it mandatory, which is also reflected in the syntax specs. in the respective man
pages: GNU Sed: -i[SUFFIX]
vs. BSD Sed -i extension
.
这篇关于shell 脚本替换文件中的变量 - Sed 的 -i 选项用于就地更新时出错的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!