shell實現config配置文件合併變更配置項

前言

通常在項目中會使用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的方式會更好。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章