【问题标题】:How do I use sed to change my configuration files, with flexible keys and values?如何使用 sed 通过灵活的键和值更改我的配置文件?
【发布时间】:2011-08-22 18:28:23
【问题描述】:

我想在配置文件中搜索此表达式:“central.database”。 然后我想将与“central.database”关联的设置更改为“SQLTEST”。

配置文件的布局最初如下所示:

central.database = SQLFIRSTTEST

这是我希望它在 sed 替换后的样子:

central.database = SQLTEST

我在 bash 脚本中执行此操作,欢迎任何建议、建议或替代解决方案!

(其实central.databaseSQLTEST都来自这里的bash变量。)


我当前的代码(第三次尝试):

sshRetValue=$(ssh -p "35903" -i $HOME/sshids/idrsa-1.old ${1} <<EOF
        sed -i "s/^\($CENTRAL_DB_NAME\s*=\s*\).*\$/\1$CENTRAL_DB_VALUE/" /home/testing.txt;
        echo $?
EOF
)

错误信息:

Pseudo-terminal will not be allocated because stdin is not a terminal.
sed: -e expression #1, char 58: unknown option to `s'
-bash: line 3: EOF: command not found

【问题讨论】:

  • 这被标记为linux,但答案大部分可移植到其他平台。 *BSD(包括 MacOS)用户希望使用sed -i "",而 Linux 只允许使用sed -i。平台之间sed语法的细节也会有差异,但简单的s/foo/bar/应该是完全可移植的。

标签: linux bash sed


【解决方案1】:
sed -i -e '/central\.database =/ s/= .*/= new_value/' /path/to/file

解释:

  • -i 告诉 sed 将结果保存到输入文件。没有它,sed 会将结果打印到标准输出。
  • /central\.database =/ 匹配包含斜线之间的字符串的行:central.database =. 被转义,因为它是正则表达式中的特殊字符。
  • s/OLD/NEW/ 部分执行 s 替换。 OLD 字符串是要匹配的正则表达式,NEW 部分是要替换的字符串。
  • 在正则表达式中,.* 表示“匹配任何内容”。所以= .* 匹配等号、空格,然后是其他任何内容。

【讨论】:

  • OP 的要求是“...central.databaseSQLTEST 都来自 bash 变量”。例如,说CENTRAL_DB_KEY=central.databaseCENTRAL_DB_VALUE=SQLTEST。您将如何处理将转义点 \. 添加到第一个 bash 变量? (也许${CENTRAL_DB_KEY//./\\.} 可以,但我还没有测试过。AIRPORT=$'O\'Hare' 呢?)
【解决方案2】:

我有一个名为“config.php”的文件,我想更改它的定义行之一。

例如,行:

define('SOME_CONSTANT', 'old_value');

必须用这个替换:

define('SOME_CONSTANT', 'new_value');

所以我这样做了:

sed -i -e "/.*SOME_CONSTANT*./ s/.*/define('SOME_CONSTANT', 'new_value');/" path/to/config.php

在第一部分中,我正在寻找包含“SOME_CONSTANT”(因此是通配符)的行

然后我用新定义替换该行,例如:define('SOME_CONSTANT', 'new_value');

在 Centos 7 中测试并运行良好

【讨论】:

    【解决方案3】:

    我知道现在为这个问题添加答案为时已晚,但是我想与大家分享我的知识。我遵循了一种非常通用的方法来解决类似的问题。我已删除与字符串匹配的整行并将所需的值添加到该键。你的问题在这里就是答案

    replaceValue=SQLTEST
    sed -i "/central.database =/d" /home/testing.txt
    echo "central.database = $replaceValue"  >> /home/testing.txt
    

    sed 从文件中删除匹配的字符串行,紧接着的下一行将所需的键和值插入到文件中。

    【讨论】:

    • 如果您不知道密钥已经存在,这是最好的答案。我认为简单性更好,您会遇到更少的逃避问题
    • 另外,最后一行不适用于 sudo。可以使用tee 代替:echo "central.database = $replaceValue" | sudo tee -a /home/testing.txt
    【解决方案4】:

    我使用这个脚本来保持优先级..

    论据 $1 将有一个文件夹,其中存在多个配置文件。 $2 将具有需要在 $1 路径和子路径文件中替换的属性 #3 将具有需要在 $2 之上覆盖的属性

    它还具有隐藏逻辑来检查 $2 和 $3 中存在的键是否存在环境变量并优先考虑。

    即,如果环境中存在最高优先级的密钥。旁边是 $3 文件,旁边是 $1 文件。

    #!/bin/bash
    #Usage is propertyReplacer <CONFIG_FOLDER_PATH> <CONFIG_FILE_2ND_PRIORITY> <CONFIG_FILE_1ST_PRIORITY>
    function propertyReplacer() {
    
      filePathToAct="$1"
      propertiesFilePath="$2"
      propertiesSecureFilePath="$3"
    
      declare -A keyValues
    
      while IFS='=' read -r key value; do
        if [  "$key" == "" ]; then
          continue
        elif [[  "$key" =~ ^#.*$ ]]; then
          continue
        else
          echo $key " --> " $value
          keyValues[$key]=$value
        fi
      done < "$propertiesFilePath"
    
      if [ ! -f "$propertiesSecureFilePath" ]; then
        continue
      else
        while IFS='=' read -r key value; do
          if [  "$key" == "" ]; then
            continue
          elif [[  "$key" =~ ^#.*$ ]]; then
            continue
          else
            echo $key " --> " $value
            keyValues[$key]=$value
          fi
        done < "$propertiesSecureFilePath"
      fi
    
      for key in ${!keyValues[@]}; do
        envProp=${key//[@]/}
        if [  "$(eval echo '$'$envProp)" == "" ]; then
          echo "Environment key not exist" $envProp
        else
          value=$(eval echo '$'$envProp)
          echo "From Environment " $envProp " --> "$value 
          keyValues[$key]=$value
        fi
      done 
    
    
    find "$filePathToAct" | while read -r resultFileName; do
      if [ ! -f "$resultFileName" ]; then
        continue
      else
        echo "Acting on the file $resultFileName"
    
        for key in ${!keyValues[@]}; do
          value=$(echo "${keyValues[${key}]}" | sed 's/\//\\\//g')
          echo "sed -i 's/$key/$value/g' $resultFileName "
          eval "sed -i 's/$key/$value/g' $resultFileName "
        done 
      fi
    done 
    
    } 
    

    【讨论】:

      【解决方案5】:

      我喜欢为此使用awk,因为它很容易理解它在做什么,并且很好地处理了分隔符(=),而且它必须在未注释的行中完成:

      awk -v var="my_var" -v new_val="NEW VALUE" \  # set the vars
          'BEGIN{FS=OFS="="}                        # set separator to =
           match($1, "^\\s*" var "\\s*") {          # check if it matches
               $2=" " new_val                       # if so, replace the line
           }1' conf_file                            # print all lines
      

      这使用match() 来检查模式是否出现在任何给定的行中。如果是,则使用给定值执行替换。

      例如:

      $ cat conf
      hello
      my_var= SOME VALUE
      #my_var = ANOTHER VALUE
      bye
      

      让我们把my_var中的值改成NEW VALUE

      $ awk -v var="my_var" -v new_val="NEW VALUE" 'BEGIN{FS=OFS="="}match($1, "^\\s*" var "\\s*") {$2=" " new_val}1' conf
      hello
      my_var= NEW VALUE
      #my_var = ANOTHER VALUE
      bye
      

      也可以在shell变量中设置值,然后与-v一起使用:

      $ var="my_var"
      $ new_value="NEW VALUE"
      $ awk -v var="$var" -v new_val="$new_value" 'BEGIN{FS=OFS="="}match($1, "^\\s*" var "\\s*") {$2=" " new_val}1' conf
      

      您当然可以将所有这些放在一个 shell 函数中,然后正常调用:

      #!/bin/bash
      
      replace () {
         file=$1
         var=$2
         new_value=$3
         awk -v var="$var" -v new_val="$new_value" 'BEGIN{FS=OFS="="}match($1, "^\\s*" var "\\s*") {$2=" " new_val}1' "$file"
      }
      
      # Call the replace() function with the necessary parameters
      replace "conf" "my_var" "NEW VALUE" 
      

      执行时,返回

      hello
      my_var= NEW VALUE
      #my_var = ANOTHER VALUE
      bye
      

      虽然您也可以让脚本以如下方式接收参数:./script.sh "conf_file" "var_to_replace" "NEW VALUE",然后将它们传递给函数。

      【讨论】:

      • @ThomasWeller 当然,也可以使用这种方法查看我的更新答案。
      【解决方案6】:

      如果你想在 2 个属性文件之间进行替换,你可以使用这个:

      awk -F= 'NR==FNR{A[$1]=$2;next}$1 in A{$2=A[$1]}1' OFS='\=' /tmp/masterfile /opt/props/finalfile.properties > /tmp/tmp.txt && mv -f /tmp/tmp.txt /opt/props/finalfile.properties
      

      【讨论】:

        【解决方案7】:

        这是一个示例表达式:

        sed -i 's/^\(central\.database\s*=\s*\).*$/\1SQLTEST/' file.cfg
        

        如果你想匹配里面有/的东西,你可以使用另一个分隔符:

        sed -i 's#^\(cent/ral\.data/base\s*=\s*\).*$#\1SQL/TEST#' file.cfg
        

        或者用变量展开:

        VAL="SQLTEST"
        sed -i "s/^\(central\.database\s*=\s*\).*\$/\1$VAL/" file.cfg
        

        在你的例子中:

        sshRetValue=`sed -i "s/^\(\1$CENTRAL_DB_NAME\s*=\s*\).*\$/\1$CENTRAL_DB_VALUE/" /home/testing.txt`;
        

        在 $CENTRAL_DB_NAME 之前有一个无效的 \1。此外, sed 不会打印它的返回值。这是检查返回值的首选方法:

        sed -i "s/^\($CENTRAL_DB_NAME\s*=\s*\).*\$/\1$CENTRAL_DB_VALUE/" /home/testing.txt;
        sed_return_value=$?
        

        最终通过管道连接到 ssh(未测试):

        sed_return_value=$(ssh server <<EOF
            sed -i "s/^\($CENTRAL_DB_NAME\s*=\s*\).*\$/\1$CENTRAL_DB_VALUE/" /home/testing.txt;
            echo $?
        EOF
        )
        

        -i 用于替换输入文件中的数据。否则 sed 写入标准输出。

        正则表达式是它们自己的一个字段。除非有一些特定的功能让您无法理解,否则无法在 stackoverflow 答案中深入解释它们。

        【讨论】:

        • OP 的要求是 "...central.databaseSQLTEST 都来自 bash 变量"。例如,说KEY=central.databaseVALUE=SQLTEST。您将如何处理将转义点 \. 添加到 bash 变量 $KEY? (也许${KEY//./\\.} 会起作用,但我还没有测试过。AIRPORT=$'O\'Hare' 呢?)
        猜你喜欢
        • 2012-10-10
        • 1970-01-01
        • 1970-01-01
        • 2021-04-24
        • 2022-07-27
        • 2016-06-15
        • 2016-06-21
        • 1970-01-01
        相关资源
        最近更新 更多