通过有条件地覆盖第二个文件的部分来合并两个文件
Merge two files by conditionally overriding parts of second file
我有两个配置文件,旧的和新的。 New 是一个带有默认值和附加变量的模板配置。 Old 是修改后的配置,其中包含必须保留的值。我需要做一个新的修改配置:
- 如果变量在新旧版本中都存在,则保留旧值
- 如果变量在旧版本中被注释掉,则保持不变,反之亦然
- 如果变量只存在于old中,删除它
- 如果变量只存在于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 分隔(因为配置编辑是手动的,我为此使用 sed
:sed -i -E 's/^#([^ ])/# /'
),所以也许 awk 可以用于整个事情。现在我有这个 awk 'FNR==NR{a[]++;next}!a[]' new_default.cfg old.cfg
写下变量名(awk 中的第一列),这对两个文件都是通用的。
============================================= ======
更新:
最后,我使用了下面的答案并对其进行了修改,因此它现在可以更好地满足我的需求,而且看起来更丑陋。
- 接受两个参数,旧配置和修补后配置的模板
- 确保在行首
#
之后有一个 space
- 确保每个
=
被一个 space 包围
- 确保每个实际评论都以两个
#
而不是一个 开头
- 发出 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
我有两个配置文件,旧的和新的。 New 是一个带有默认值和附加变量的模板配置。 Old 是修改后的配置,其中包含必须保留的值。我需要做一个新的修改配置:
- 如果变量在新旧版本中都存在,则保留旧值
- 如果变量在旧版本中被注释掉,则保持不变,反之亦然
- 如果变量只存在于old中,删除它
- 如果变量只存在于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 分隔(因为配置编辑是手动的,我为此使用 sed
:sed -i -E 's/^#([^ ])/# /'
),所以也许 awk 可以用于整个事情。现在我有这个 awk 'FNR==NR{a[]++;next}!a[]' new_default.cfg old.cfg
写下变量名(awk 中的第一列),这对两个文件都是通用的。
============================================= ======
更新: 最后,我使用了下面的答案并对其进行了修改,因此它现在可以更好地满足我的需求,而且看起来更丑陋。
- 接受两个参数,旧配置和修补后配置的模板
- 确保在行首
#
之后有一个 space - 确保每个
=
被一个 space 包围 - 确保每个实际评论都以两个
#
而不是一个 开头
- 发出 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