前言
通常在項目中會使用config文件作爲項目的配置文件,config文件一般由[section]
和name=value
組成。當然分隔符=
或者:
是可以根據自己來定的。
文件的格式通常如下:
[DEFAULT]
service_phone=18888888888
# 資源路徑
resource_dir=/xxx/xxx/xxx/
# 服務端口
server_port=xxx
#WEB配置
[HTTP-SERVER]
host=0.0.0.0
http_port=xxx
對於該格式文件的解析,Python3有專門的庫處理:configparser
,通過導入import configparser
就可以解析使用了。
爲什麼採用增量配置conf文件?
回到主題,既然config文件是用來保存項目配置的,那麼什麼時候會合並A配置文件到B配置文件呢?比如:發佈項目的時候,項目release_1.0.0的版本已經發布,項目上的配置文件已經存在,在發佈release_2.0.0的版本的時候,爲了防止原配置文件的配置會被覆蓋,導致原配置丟失,所以使用增量配置的方式來更改配置文件。
比如:
release_1.0.0版本的文件app.conf文件如下:
[SYS_CONFIG]
# 服務端口
server_port=8888
# 資源路徑
resource_dir=/home/zhangsan/resource/
[USER_INFO]
# 姓名
name=張三
# 電話
phone=18888888888
# 住址
address=馬欄山馬欄坡馬欄屯123號
然後該項目需要發佈release_2.0.0版本,並且配置文件如下:
[SYS_CONFIG]
# 服務端口
server_port=8080
# 資源路徑
resource_dir=/home/zhangsan/resource2/
由於發佈版本時,開發人員可能只是想更改[SYS_CONFIG]
部分的配置,但是不小心把[USER_INFO]
部分的配置刪除了,導致發佈2.0版本之後線上配置文件被直接覆蓋刪除,導致出現問題。爲了讓每次發佈只需要關心配置文件需要更改的部分,而不關心未更改的配置,解決配置文件輕易被覆蓋刪除的問題,那麼採用增量配置的方式更加的穩妥。
比如接着上面的例子,發佈2.0版本的配置文件只會關心發佈更改的配置文件項,由於只列出了[SYS_CONFIG]
的配置,所以只會更改線上原配置文件中的[SYS_CONFIG]
中的配置,原配置文件的[USER_INFO]
的配置依然存在不變。
增量變更配置的幾種類型
分幾種變更場景
新增[section]
A.conf
[SECTION_A]
opentionA_1=valueA
opentionA_2=valueA
B.conf
[SECTION_B]
opentionB_1=valueB
opentionB_2=valueB
合併B.conf到A.conf之後的內容:
[SECTION_A]
opentionA_1=valueA
opentionA_2=valueA
[SECTION_B]
opentionB_1=valueB
opentionB_2=valueB
修改配置項
A.conf
[SECTION_A]
opentionA_1=valueA
opentionA_2=valueA
B.conf
[SECTION_A]
opentionA_1=被修改了
合併B.conf到A.conf之後的內容:
[SECTION_A]
opentionA_1=被修改了
opentionA_2=valueA
刪除配置項
刪除配置項爲了穩妥起見,採用將value值去除變爲空值的方式,起到刪除的作用。
A.conf
[SECTION_A]
opentionA_1=valueA
opentionA_2=valueA
B.conf
[SECTION_A]
opentionA_1=
合併B.conf到A.conf之後的內容:
[SECTION_A]
opentionA_1=
opentionA_2=valueA
新增配置項
A.conf
[SECTION_A]
opentionA_1=valueA
opentionA_2=valueA
B.conf
[SECTION_A]
opentionA_3=新增配置項3
合併B.conf到A.conf之後的內容:
[SECTION_A]
opentionA_1=valueA
opentionA_2=valueA
opentionA_3=新增配置項3
混合變更配置
A.conf
[SECTION_A]
opentionA_1=valueA
opentionA_2=valueA
B.conf
[SECTION_A]
opentionA_3=新增配置項3
opentionA_1=被修改了
opentionA_2=
[SECTION_B]
opentionB_1=valueB
opentionB_2=valueB
合併B.conf到A.conf之後的內容:
[SECTION_A]
opentionA_1=被修改了
opentionA_2=
opentionA_3=新增配置項3
[SECTION_B]
opentionB_1=valueB
opentionB_2=valueB
shell實現config配置文件的增量變更
shell實現的核心是使用awk命令讀取分析A文件和B文件的內容,並確定B文件的每個配置項分別變更了A文件的哪一行配置或者在哪一行後面新增配置。然後將變更信息生成sed腳本,最後直接用sed命令替換配置內容。最後的sed命令採用流式編輯文件,因此非常的高效。
mconf.sh
#!/bin/bash
fcf_path=$1 # 配置文件路徑
to_mcf_path=$2 # 合併目標文件
sed_script=$3 # sed腳本
#awk 分別讀取兩個配置文件
#比較文件,找出每項配置合併到mcf的增刪改並寫入到sed文件
#使用sed將mcf文件變更
# 遇到section插入
if [ ! -f "$fcf_path" ]; then
echo "配置文件不存在:${fcf_path}"
exit 1
fi
if [ ! -f "$to_mcf_path" ]; then
echo "目標配置文件不存在:${to_mcf_path}"
exit 1
fi
# linux環境中去除Windows中的\r符號
sed -i 's/\r//g' "$fcf_path"
sed -i 's/\r//g' "$to_mcf_path"
cat /dev/null >"$sed_script" # 清空sed腳本文件
function cf_append() {
# 附加插入 格式: 行號a\換行 附加內容
local row_num=$1 #行號
local content=$2 #附加內容
{
echo "${row_num}a\\"
echo "${content}"
echo "" #還需要一個空行,否則附加內容之後的內容會在一行
} >>"$sed_script"
}
function cf_change() {
# 更新配置 格式: 行號s/模式匹配/替換內容/g 模式匹配最好採用整行匹配xx=.*
local row_num=$1 #行號
local pattern=$2 #匹配字符串
local content=$3 #替換內容
if [[ $content == */* ]]; then
{
echo "${row_num}s!${pattern}!${content}!g" >>"$sed_script"
}
else
{
echo "${row_num}s/${pattern}/${content}/g" >>"$sed_script"
}
fi
}
function act_sed() {
# 觸發sed操作
if [ ! -s "$to_mcf_path" ]; then
echo -n "#head" >>"$to_mcf_path" # 如果文件爲空,則需要添加一個頭部內容才能用sed命令,否則sed命令無效
fi
sed -i -f "$sed_script" "$to_mcf_path"
}
function match_tconf() {
# 匹配目標項的配置,如果有多個相同配置則去最後的配置
local key=$1
local IFS_OLD=$IFS
match_rows=$(awk -F= '{if ($1 == key) print NR,$1,$2}' key="$key" "$to_mcf_path")
if [ -z "$match_rows" ]; then #如果沒有匹配項
echo ""
return
fi
IFS=$'\n'
amatch_rows=($match_rows)
local last_row=${amatch_rows[((${#amatch_rows[*]})) - 1]} #獲取最後一行
echo "$last_row"
IFS=$IFS_OLD
}
function compare_conf() {
#比較配置文件,並生成sed腳本
f_rows=$(awk -F= '{print $0}' "$fcf_path")
IFS_OLD=$IFS
IFS=$'\n'
last_section_num=0 #最近一次的section行號,$爲最後一行。因爲流式處理配置文件,所以所有項一定是按序處理
for frow in $f_rows; do
if [ -z "$frow" ]; then
continue
fi
IFS=$'='
fcolumns=($frow)
local key=${fcolumns[0]}
local fvalue=${fcolumns[1]}
match_row=$(match_tconf "$key") #匹配目標配置文件中的內容
IFS=$' '
amatch_column=($match_row) #行號 key value
if ((${#amatch_column[*]} == 2)); then
amatch_column=($amatch_column "")
fi
if [[ $key == [* ]]; then #如果是section
if [ -z "$match_row" ]; then #如果section未在目標配置中找到,則表示爲新增section
cf_append '$' ""
cf_append '$' "$key"
last_section_num='$'
else
local lnum=${amatch_column[0]} #section處在目標配置中的行號
last_section_num=$lnum
fi
continue #繼續循環
fi
if [ -z "$match_row" ]; then # 如果沒有找到匹配項
# 新增
if [[ $frow != *=* ]]; then #如果非配置項
if [[ $frow != [* ]]; then
continue
fi
cf_append "$last_section_num" "$key"
else
cf_append "$last_section_num" "$key=$fvalue"
fi
elif [ "${amatch_column[((${#amatch_column[*]})) - 1]}" != "$fvalue" ]; then
# 修改
local lnum=${amatch_column[0]} #目標文件匹配的行號
cf_change "$lnum" "$key=.*" "$key=$fvalue"
fi
done
IFS=$IFS_OLD
}
compare_conf
act_sed
腳本執行:
./mconf.sh ./b.conf ./a.conf ./m.sed
最終會生成會將b.conf文件合併到a.conf文件,並且生成m.sed文件,m.sed文件會作爲sed
命令的-f
參數傳入並執行。
注意如果你是macOS,那麼sed命令後面需要帶上-i ‘’ ,因爲mac系統強制需要你傳入備份文件名。
腳本中讀取配置文件採用的分隔符分隔的方式,可以使用while-read的方式會更好。