如何使用sed更改配置文件中灵活的键和值?

54

我想在配置文件中搜索以下表达式:"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

这被标记为 [tag:linux],但答案在大多数情况下也适用于其他平台。*BSD(包括MacOS)用户想要使用 sed -i "",而Linux只允许使用 sed -i。不同平台之间的 sed 语法细节也会有所不同,但简单的 s/foo/bar/ 应该是完全可移植的。 - tripleee
8个回答

120
sed -i -e '/central\.database =/ s/= .*/= new_value/' /path/to/file

解释:

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

OP的要求是“...central.databaseSQLTEST都来自bash变量”。例如,假设CENTRAL_DB_KEY=central.databaseCENTRAL_DB_VALUE=SQLTEST。您将如何处理向第一个bash变量添加转义点\.?(也许${CENTRAL_DB_KEY//./\\.}可以工作,但我还没有测试过。那么AIRPORT=$'O\'Hare'呢?) - Joseph Quinsey
对于想要更改特定部分下键的值的人,例如:[sectionA] central.database = new_val,您可以使用sed的范围地址来实现:sed -i -e '/\[sectionA\]/, /central\.database =/ s/= .*/= new_value/' /path/to/file - Hua2308

74

以下是一个表达式的示例:

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`;

\1之前有一个无效的字符,这个字符是在$CENTRAL_DB_NAME之前的。此外,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会将结果写入stdout。

正则表达式是一个独立的领域。除非有一些具体的函数令您困扰,否则在stackoverflow答案中深入解释它们是不可能的。


OP的要求是“...central.databaseSQLTEST都来自bash变量”。例如,假设KEY=central.databaseVALUE=SQLTEST。您将如何处理向bash变量$KEY添加转义点\.?(也许${KEY//./\\。}可以工作,但我还没有测试过。那么AIRPORT=$'O\'Hare'呢?) - Joseph Quinsey

10

我知道现在回答这个问题已经太晚了,但是我想和大家分享我的经验。我有一个非常通用的方法来解决类似的问题。我删除了与字符串匹配的整行,并向该键添加所需的值。至于你的问题,这就是答案。

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

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


3
如果您不知道密钥是否已存在,那么这是最好的答案。我认为它更简单易懂,并且您将遇到较少的转义问题。 - user17130
另外,最后一行不能使用sudo。可以使用tee代替:echo "central.database = $replaceValue" | sudo tee -a /home/testing.txt - Aurel Branzeanu

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 当然,可以看看我更新后的答案,其中也包括了这种方法。 - fedorqui
除非我漏掉了什么,这只匹配开头部分。例如,replace my_conf.txt "foo" 1 将替换以 foo 开头的任何值。 - sfrank
根据@sfrank的提及,如果你在match($1, "^\s*" var "\s*$")的末尾添加$符号,它也会匹配变量名的部分。我在末尾添加了| grep $var,这样输出就不再是整个文件,而只是相关的行候选项。 - HaPi

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

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中测试并且工作正常。

1

工作示例

假设我们想要更新一个Postgres配置文件以增加连接数。

我们想要更新/var/lib/postgresql/data/postgresql.conf中的这一行:

max_connections = 100

使用 sed 命令:

sed -i 's/max_connections = 100/max_connections = 250/g' /var/lib/postgresql/data/postgresql.conf

当更新Docker配置文件时,这非常有用,因为任何更新都需要自动化。


0

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

参数 $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 

} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接