通过有条件地覆盖第二个文件的部分来合并两个文件

Merge two files by conditionally overriding parts of second file

我有两个配置文件,旧的和新的。 New 是一个带有默认值和附加变量的模板配置。 Old 是修改后的配置,其中包含必须保留的值。我需要做一个新的修改配置:

  1. 如果变量在新旧版本中都存在,则保留旧值
  2. 如果变量在旧版本中被注释掉,则保持不变,反之亦然
  3. 如果变量只存在于old中,删除它
  4. 如果变量只存在于new中,保留它

旧修改

$ cat old.cfg
# var1 = 111
# var2 = 123
var3 = 111
var4 = 123
var5 = 123

新的默认配置

$ cat new_default.cfg
var1 = 111
# var2 = 123
var3 = 111
# var4 = 111
var6 = 111

新修改的配置(所需)

$ cat new.cfg
# var1 = 111
# var2 = 123
var3 = 111
var4 = 123
var6 = 111

# 总是用白色 space 分隔(因为配置编辑是手动的,我为此使用 sedsed -i -E 's/^#([^ ])/# /'),所以也许 awk 可以用于整个事情。现在我有这个 awk 'FNR==NR{a[]++;next}!a[]' new_default.cfg old.cfg 写下变量名(awk 中的第一列),这对两个文件都是通用的。

============================================= ======

更新: 最后,我使用了下面的答案并对其进行了修改,因此它现在可以更好地满足我的需求,而且看起来更丑陋。

  1. 接受两个参数,旧配置和修补后配置的模板
  2. 确保在行首 # 之后有一个 space
  3. 确保每个 = 被一个 space 包围
  4. 确保每个实际评论都以两个 # 而不是一个
  5. 开头
  6. 发出 awk 命令:如果行以 # 开头 -> 比较第二列; 以 ## 开头 -> 比较整行;开始时没有 # -> 先比较 列

.

#!/bin/bash
for var in "$@"
do
    cp $var $var.bak
    sed -i -E 's/^#([^ ])/# /' $var
    sed -i -E 's/(.?)(\s?)=(\s?)(.?)/ = /' $var
    sed -i -E 's/^#([^=]+)$/##/' $var
done
awk '{if(/^# /)k=;else if(/^## /)k=[=13=];else k=;}NR==FNR{a[k]=[=13=]; next} 
{print (k in a)?a[k]:[=13=]}'   > output.txt

如果您想实现与特定于应用程序的逻辑的合并,获得所需逻辑的唯一可靠方法是您自己实际构建它。因此:

#!/usr/bin/env bash
case $BASH_VERSION in ''|[123].*) echo "ERROR: Bash 4.0 or newer required" >&2; exit 1;; esac

declare -A old new

read_to_array() {
  local line
  local -n dest=
  local -n comment_dest=
  declare -g -A "" ""
  while IFS= read -r line; do
    case $line in
      "")          continue;;
      "#"*" = "*)  line=${line#"#"};
                   comment_dest[${line%%" = "*}]=$line;;
      "#"*)        continue;;
      *" = "*)     dest[${line%%" = "*}]=${line#*" = "};;
      *)           echo "Ignoring unrecognized line: $line" >&2
    esac
  done
}

read_to_array old old_comments <old.txt
read_to_array new new_comments <new.txt
declare -A done=( )

for key in "${!new[@]}"; do
  # if commented out in old, leave it that way
  if [[ ${old_comments[$key]} ]]; then
    echo "#$key = ${new[$key]}"
    continue
  fi
  # key exists in both old and new; use old
  if [[ ${old[$key]} ]]; then
    echo "$key = ${old[$key]}"
    continue
  fi
  # key is only in new; keep it
  echo "$key = ${new[$key]}"
done

for key in "${!new_comments[@]}"; do
  # if present at all in old, we were already emitted
  [[ ${old[$key]} ]] && continue
  echo "${new_comments[$key]}"
done

awk 救援!

$ awk       '{k=/^#/?:} 
     NR==FNR {a[k]=[=10=]; next}
             {print (k in a)?a[k]:[=10=]}' config.old config.new

# var1 = 111
# var2 = 123
var3 = 111
var4 = 123
var6 = 111

尽管不确定所有测试都包含在您的样本中input/output。

此答案假定您始终在 #= 周围留有空格,如示例输入中所示:

awk '
    NR == FNR {if ( == "#") new_ignore[]; else new[] = ; next }
     == "#" { delete new[]; print; next }
    { old[] }
     in new ||  in new_ignore { print; next }
    END { for (key in new) if (!(key in old)) printf "%s = %s\n", key, new[key] }
' new_default.cfg old.cfg
# var1 = 111
# var2 = 123
var3 = 111
var4 = 123
var6 = 111