bash sed:更改 .yml 文件中环境属性的值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5723464/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-17 23:49:45  来源:igfitidea点击:

sed: change values of properties of an environment in a .yml file

regexparsingbashsed

提问by Lourenco

I have an .yml file that configures environment properties of an application, such like this:

我有一个 .yml 文件,用于配置应用程序的环境属性,例如:

env1:
  :prop1: "value1"
  :prop2: "value2"
        ...
  :propn: "valuen"

env2:
  :prop1: "value1"
  :prop2: "value2"
  :prop3: "value3"
        ...
  :propn: "valuen"

    ...

envn:
  :prop1: "value1"
  :prop2: "value2"
        ...
  :propn: "valuen"

I would like to produce a bash script with the following interface:

我想生成一个具有以下界面的 bash 脚本:

$ change_env.sh <environment> <property> <new value> <file.yml>

Example:

例子:

$ change_env.sh env2 prop3 "this value was changed" file.yml

The output will be:

输出将是:

env1:
  :prop1: "value1"
  :prop2: "value2"
        ...
  :propn: "valuen"

env2:
  :prop1: "value1"
  :prop2: "value2"
  :prop3: "this value was changed"
        ...
  :propn: "valuen"

    ...

envn:
  :prop1: "value1"
  :prop2: "value2"
        ...
  :propn: "valuen"

I found this post, however I could not do it work for my case. Replace an XML element's value? Sed regular expression?

我找到了这篇文章,但是我无法为我的案例工作。 替换 XML 元素的值?sed正则表达式?

I also tried this: (it fails because alters all properties)

我也试过这个:(它失败了,因为改变了所有的属性)

sed 's/\(:pro3:\).*/ "new value"/'

Thanks in advance! -- Lourenco.

提前致谢!——洛伦科。

采纳答案by shellter

(very nice first post!)

(非常好的第一篇文章!)

Try this

尝试这个

cat change_env.sh

#!/bin/bash
# spec : change_env.sh <environment> <property> <new value> <file.yml>

case ${#} in [!4] ) 
    echo "usage: change_env.sh <environment> <property> <new value> <file.yml>" 1>&2
    exit 1 
   ;; 
esac

env="" prop="" new="" file=""
bakFile="${file}".bak
mv "$file" "$bakFile"
sed '/^'"${env}"'/,/^[   ]*$/{  # [ spaceChar tabChar ]
        /'"${prop}"'/s/\('"${prop}"'\)\(.*$\)/'"${new}"'/
    }' "$bakFile" > "$file"

edit

编辑

Note, if you expect input to contain white-space in the values you'll want to modify script to quote all variables ("$1","$2"...). (I have now done this, as it is a shell-scripting best practice).

请注意,如果您希望输入在值中包含空格,您需要修改脚本以引用所有变量(“$1”、“$2”...)。(我现在已经这样做了,因为它是一个 shell 脚本最佳实践)。

The /env/,/^[{space,tab}]*$/is a range address for sed. It reads a block of text that contains your environment settings. I'm assuming your sample input is correct and that each env is separated by a blank line. Hmm... this would include the last one in the file.

/env/,/^[{space,tab}]*$/对sed的是一个范围的地址。它读取包含您的环境设置的文本块。我假设您的示例输入是正确的,并且每个 env 由一个空行分隔。嗯...这将包括文件中的最后一个。

** edit**

** 编辑**

Thanks to @posdef for pointing some problems with this answer. The code is updated to solve the particular case.

感谢@posdef 指出了这个答案的一些问题。更新代码以解决特定情况。

Even after the fix, I did notice that given an input like

即使在修复之后,我确实注意到给出了一个输入

   change_env.sh env2 prop2 "new value" file.yml

The relevant output was

相关输出是

     :prop2new value

So, without hardcoding extra :and space chars into the substitution, this means you'll need to be very verbose in how you call the <property>value AND the <new value>, i.e.

因此,如果没有将额外:和空格字符硬编码到替换中,这意味着您需要非常详细地调用<property>值和<new value>,即

   change_env.sh env2 ":prop2: " "\"new value\"" file.yml
   # note extra cruft-^^-----^^^--^^---------^^--------------

relevant output

相关输出

env2:
  :prop1: "value1"
  :prop2: "new value"
  :prop3: "value3"
    ...
  :propn: "valuen"

IHTH

IHTH

回答by glenn Hymanman

I'd use awk:

我会使用 awk:

#!/bin/sh

if [ $# -ne 4 ]; then
    echo "usage: 
#!/bin/sh

if [ $# -ne 4 ]; then
    echo "usage: 
#!/bin/bash
# tested with bash 4
if [ $# -ne 4 ];then
    echo "Usage: .... "
    exit
fi
env=
prop=
text=""
file=
while read -r line
do
    case "$line" in
        "$env"* )
        toggle=1
        ;;
    esac
    if [ "$toggle" = 1 ];then
        if [[ $line =~ "$prop" ]] ;then
            line="${line%%\"*}\"$text\""
            toggle=0
        fi
    fi
    echo "$line"
done < $file > t
mv t $file
env prop value file" >&2 exit 1 fi awk -F : -v env="" -v prop="" -v val=" " ' function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s } function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s } function trim(s) { return rtrim(ltrim(s)); } BEGIN {OFS = FS} == env {in_env = 1} NF == 0 {in_env = 0} in_env && trim() == prop { = val} {print} ' ""
env prop value file" >&2 exit 1 fi awk -F : -v env="" -v prop="" -v val=" \"\"" ' BEGIN {OFS = FS} == env {in_env = 1} NF == 0 {in_env = 0} in_env && == prop { = val} {print} ' ""

回答by posdef

This answer is based on Glenn Hymanman's AWK script, which in my testing fails due to indentation issues inherent in the type of input OP (and yours truly) has.

这个答案基于 Glenn Hymanman 的 AWK 脚本,在我的测试中,由于输入 OP(和您的)类型固有的缩进问题,该脚本失败。

Specifically, the condition of checking whether or not we are in the desired environment will happen on a different iteration than checking whether or not we have the desired property since these will typically be on different lines. Thus in_env && $2 == propwill never return true, considering that property : valuepair will be read as $1 : $2on a separate line.

具体来说,检查我们是否处于所需环境中的条件将发生在与检查我们是否具有所需属性不同的迭代中,因为这些通常位于不同的行上。因此in_env && $2 == prop永远不会返回 true,考虑到该property : value对将$1 : $2在单独的行上读取。

Additionally, the comparison $2 == propwill suffer from the leading whitespace, which needs to be trimmed. I've added a couple of nice one-liners described here, for making the script more human-readable.

此外,比较$2 == prop将受到需要修剪的前导空格的影响。我添加了这里描述的几个不错的单行代码,以使脚本更具人类可读性。

Lastly, the original script hard-coded double quotes around the new value, which is a problem if you are inserting numerical values.

最后,原始脚本硬编码了新值周围的双引号,如果您插入数值,这是一个问题。

I have modified the script in the following way which works well in my test case. I am providing here in case it is of use to others.

我已经按照以下方式修改了脚本,这在我的测试用例中运行良好。我在这里提供以防对其他人有用。

use strict;
use warnings;

my $str = '
env1:
  :prop1: "value1"
  :prop2: "value2"
        ...
  :propn: "valuen"

env2:
  :prop1: "value1"
        ...
  :prop2: "value2"
  :prop3: "value3"
  :propn: "valuen"
    ...

envn:
  :prop1: "value1"
        ...
  :prop3: "value3"
  :propn: "valuen"
';

my ($env, $prop, $replacement) = ('(?:env2|envn)', 'prop1', 'this changed');

if ( $str =~ s/
    (
      \s*$env:\s*
      (?: (?!\s*[^\W_]+:) [^\n]*\s* )*
      \s*:$prop:[^\S\n]*
      "
    ) [^\n]*
    ( " )
  /$replacement/xg )
{
     print "Found it!\n";
     print $str;
}

回答by bash-o-logist

Bash script

Bash 脚本

Found it!

env1:
  :prop1: "value1"
  :prop2: "value2"
        ...
  :propn: "valuen"

env2:
  :prop1: "this changed"
        ...
  :prop2: "value2"
  :prop3: "value3"
  :propn: "valuen"

    ...

envn:
  :prop1: "this changed"
        ...
  :prop3: "value3"
  :propn: "valuen"

回答by bash-o-logist

Your going to need at least one lookahead assertion. There are many ways to do it, but you should use something that knows how to parse this.

您至少需要一个前瞻断言。有很多方法可以做到这一点,但您应该使用知道如何解析它的东西。

s/(\s*$env:\s*(?:(?!\s*[^\W_]+:)[^\n]*\s*)*\s*:$prop:[^\S\n]*")[^\n]*(")/$1$replacement$2/g

s/(\s*$env:\s*(?:(?!\s*[^\W_]+:)[^\n]*\s*)*\s*:$prop:[^\S\n]*")[^\n]*(")/$1$replacement$2/g

Mildly tested in perl:

在 perl 中进行了轻度测试:

##代码##

Output:

输出:

##代码##