2

这是我的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 . 

我该如何解决?

4

3 回答 3

3

tl;博士

使用BSD Sed(例如在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/macOS sed,其-i选项需要一个选项参数,该参数指定要创建的备份文件的后缀。
因此,您的sed 脚本(与您的期望相反)被解释为-i'选项参数(备份后缀),而您的输入文件名被解释为script,这显然失败了。

相比之下,您的命令使用GNU sed语法,-i可以单独使用 where 来指示保留要就地更新的输入文件的备份文件。

等效的BSD sed选项是-i ''- 注意技术需要使用单独的参数来指定 option-argument '',因为它是空字符串(如果使用-i'',shell 会简单地剥离''之前sed看到的它:-i''实际上与刚才相同-i)。

遗憾的是,这不适用于GNU sed,因为它仅在直接附加到时识别选项参数-i,并将单独的参数解释''为单独的参数,即脚本

这种行为差异源于-i选项实现背后的根本不同的设计决策,并且由于向后兼容性的原因,它可能不会消失。[1]

如果您不想创建备份文件,那么没有一种-i语法适用于BSD和 GNU sed

有四个基本选项:

  • (a) 如果您知道您只会使用GNUBSD,请相应地sed构建-i选项:-ifor GNU sed-i ''for BSD sed

  • (b) 指定一个非空后缀作为-i's option-argument,如果您将其直接附加到-i选项,则适用于两种实现;例如,-i'.bak'。虽然这总是会创建一个带有 suffix 的备份文件.bak,但您可以在之后将其删除。

  • (c) 在运行时确定sed您正在处理的实现并-i相应地构造选项。

  • (d)-i完全省略(不符合 POSIX 标准),并使用临时文件替换成功时的原始文件:sed '...' "$Deploy_path" > tmp.out && mv tmp.out "$Deploy_path".
    请注意,这实际上是在-i幕后所做的,它可能会产生意想不到的副作用,特别是作为符号链接的输入文件被替换为常规文件;,但是,确实保留了原始文件的某些属性:请参阅我的这个答案-i的下半部分。

这是bash(c) 的一个实现,它还简化了原始代码(sed使用 2 次替换的单次调用)并使其更加健壮(变量被双引号括起来):

#!/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脚本中是安全的,但如果值包含、 、 或换行符的实例&,则需要预先转义以免破坏命令。 有关如何执行通用预转义的信息,请参阅我的这个答案,但此时您也可以考虑使用其他工具,例如and 。/\sed
awkperl


[1] GNU Sed 将 option-argument 视为 -i optional,而 BSD Sed 则认为它是optional ,这也反映在语法规范中。在相应的man页面中: GNU Sed: -i[SUFFIX]vs. BSD Sed -i extension

于 2016-11-24T03:53:57.867 回答
1
ex -sc '%!awk "\
\$1 == \"RABBITMQ_HOST\" && \$2 = \"rabbitmq1\"\
\$1 == \"RABBITMQ_PASS\" && \$2 = 12345\
" FS== OFS==' -cx file
  1. POSIX Sed不支持该-i选项。但是 ex 可以就地编辑文件

  2. awk 是一个更好的工具,因为数据被分成记录和字段

  3. 无论是 Sed 还是 Awk,您都可以使用换行符或;在一次调用中完成所有操作

  4. 您有内部没有变量的双引号字符串,不妨使用单引号

  5. 当文件名没有需要转义的字符时,您引用了文件名

  6. 您有几个未引用的变量用法,几乎不是一个好主意

于 2016-11-24T02:56:06.787 回答
0

简单案例

如果test.env只包含这两个变量,您可以简单地创建一个新文件,或覆盖现有文件:

printf "RABBITMQ_HOST=%s\nRABBITMQ_PASS=%s\n" \
  "${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与所获取的文件不受信任一样有风险。想想#include <file>C 或include "file.php"PHP。这些指令也将源包含到当前源中。所以不要盲目地考虑采购文件作为反模式。这一切都取决于具体情况。如果您的test.env是正在部署的存储库的一部分,那么调用source test.env. 不过,这是我的看法。

于 2016-11-24T03:26:06.180 回答