diff --git a/Deployment/conf_upgrade.sh b/Deployment/conf_upgrade.sh index f8edde5..8859291 100755 --- a/Deployment/conf_upgrade.sh +++ b/Deployment/conf_upgrade.sh @@ -249,6 +249,21 @@ elif grep -q 'HOSTN' "$TEMPLATE" 2>/dev/null; then fi fi +# ── Block-parsing patterns ─────────────────────────────────────────────────────────────────── +# All three walkers below share these. A single-line array — KEY=(a b c) — both opens and +# closes on one line. Matching only the opener leaves the walker inside a block it never +# leaves, so every key until the next standalone ")" becomes invisible: skipped by +# _collect_stats, dropped from HOST_MAP by _parse_target, and emitted from the template +# instead of the user's conf by _write_merged. _ARR_ONELINE_RE is what stops that. + +_ARR_DECL_RE='^[[:space:]]*declare[[:space:]]+-[a-zA-Z]+[[:space:]]+([A-Z0-9_]+)[[:space:]]*=\(' +_ARR_OPEN_RE='^[[:space:]]*([A-Z0-9_]+)[[:space:]]*=\(' +_ARR_CLOSE_RE='^[[:space:]]*\)[[:space:]]*(#.*)?$' +# The [^#]* is deliberate: it forces the closing ")" to appear before any comment, so an +# opener like FOO=( # see note (here) is not mistaken for a complete single-line array. +_ARR_ONELINE_RE='=\([^#]*\)[[:space:]]*(#.*)?$' +_SCALAR_RE='^[[:space:]]*([A-Z0-9_]+)[[:space:]]*=' + # ── Parse target → KEY → full definition block ─────────────────────────────────────────────── declare -A HOST_MAP # KEY → complete definition line(s) from user's conf @@ -260,19 +275,23 @@ _parse_target() { if [[ "$in_block" == true ]]; then cur_block+="$line"$'\n' # Closing ) — optional trailing whitespace and comment - if [[ "$line" =~ ^[[:space:]]*\)[[:space:]]*(#.*)?$ ]]; then + if [[ "$line" =~ $_ARR_CLOSE_RE ]]; then HOST_MAP["$cur_key"]="$cur_block" in_block=false; cur_key=""; cur_block="" fi else - # declare -A KEY=( - if [[ "$line" =~ ^[[:space:]]*declare[[:space:]]+-[a-zA-Z]+[[:space:]]+([A-Z0-9_]+)[[:space:]]*=\( ]]; then - cur_key="${BASH_REMATCH[1]}"; in_block=true; cur_block="$line"$'\n' - # KEY=( - elif [[ "$line" =~ ^[[:space:]]*([A-Z0-9_]+)[[:space:]]*=\( ]]; then - cur_key="${BASH_REMATCH[1]}"; in_block=true; cur_block="$line"$'\n' + # declare -A KEY=( / KEY=( + if [[ "$line" =~ $_ARR_DECL_RE ]] || [[ "$line" =~ $_ARR_OPEN_RE ]]; then + cur_key="${BASH_REMATCH[1]}" + # KEY=(a b c) — opens and closes on one line, never enter block mode + if [[ "$line" =~ $_ARR_ONELINE_RE ]]; then + HOST_MAP["$cur_key"]="$line"$'\n' + cur_key="" + else + in_block=true; cur_block="$line"$'\n' + fi # KEY=value (simple scalar) - elif [[ "$line" =~ ^[[:space:]]*([A-Z0-9_]+)[[:space:]]*= ]]; then + elif [[ "$line" =~ $_SCALAR_RE ]]; then HOST_MAP["${BASH_REMATCH[1]}"]="$line"$'\n' fi fi @@ -289,7 +308,7 @@ _collect_stats() { while IFS= read -r line || [[ -n "$line" ]]; do if [[ "$in_block" == true ]]; then - if [[ "$line" =~ ^[[:space:]]*\)[[:space:]]*(#.*)?$ ]]; then + if [[ "$line" =~ $_ARR_CLOSE_RE ]]; then in_block=false TMPL_SEEN["$cur_key"]=1 if [[ -n "${HOST_MAP[$cur_key]+_}" ]]; then KEPT+=("$cur_key") @@ -297,11 +316,17 @@ _collect_stats() { cur_key="" fi else - if [[ "$line" =~ ^[[:space:]]*declare[[:space:]]+-[a-zA-Z]+[[:space:]]+([A-Z0-9_]+)[[:space:]]*=\( ]]; then - cur_key="${BASH_REMATCH[1]}"; in_block=true - elif [[ "$line" =~ ^[[:space:]]*([A-Z0-9_]+)[[:space:]]*=\( ]]; then - cur_key="${BASH_REMATCH[1]}"; in_block=true - elif [[ "$line" =~ ^[[:space:]]*([A-Z0-9_]+)[[:space:]]*= ]]; then + if [[ "$line" =~ $_ARR_DECL_RE ]] || [[ "$line" =~ $_ARR_OPEN_RE ]]; then + cur_key="${BASH_REMATCH[1]}" + if [[ "$line" =~ $_ARR_ONELINE_RE ]]; then + TMPL_SEEN["$cur_key"]=1 + if [[ -n "${HOST_MAP[$cur_key]+_}" ]]; then KEPT+=("$cur_key") + else ADDED+=("$cur_key"); fi + cur_key="" + else + in_block=true + fi + elif [[ "$line" =~ $_SCALAR_RE ]]; then local k="${BASH_REMATCH[1]}" TMPL_SEEN["$k"]=1 if [[ -n "${HOST_MAP[$k]+_}" ]]; then KEPT+=("$k") @@ -324,18 +349,23 @@ _write_merged() { while IFS= read -r line || [[ -n "$line" ]]; do if [[ "$in_block" == true ]]; then cur_block+="$line"$'\n' - if [[ "$line" =~ ^[[:space:]]*\)[[:space:]]*(#.*)?$ ]]; then + if [[ "$line" =~ $_ARR_CLOSE_RE ]]; then in_block=false if [[ -n "${HOST_MAP[$cur_key]+_}" ]]; then printf '%s' "${HOST_MAP[$cur_key]}" else printf '%s' "$cur_block"; fi cur_key=""; cur_block="" fi else - if [[ "$line" =~ ^[[:space:]]*declare[[:space:]]+-[a-zA-Z]+[[:space:]]+([A-Z0-9_]+)[[:space:]]*=\( ]]; then - cur_key="${BASH_REMATCH[1]}"; in_block=true; cur_block="$line"$'\n' - elif [[ "$line" =~ ^[[:space:]]*([A-Z0-9_]+)[[:space:]]*=\( ]]; then - cur_key="${BASH_REMATCH[1]}"; in_block=true; cur_block="$line"$'\n' - elif [[ "$line" =~ ^[[:space:]]*([A-Z0-9_]+)[[:space:]]*= ]]; then + if [[ "$line" =~ $_ARR_DECL_RE ]] || [[ "$line" =~ $_ARR_OPEN_RE ]]; then + cur_key="${BASH_REMATCH[1]}" + if [[ "$line" =~ $_ARR_ONELINE_RE ]]; then + if [[ -n "${HOST_MAP[$cur_key]+_}" ]]; then printf '%s' "${HOST_MAP[$cur_key]}" + else printf '%s\n' "$line"; fi + cur_key="" + else + in_block=true; cur_block="$line"$'\n' + fi + elif [[ "$line" =~ $_SCALAR_RE ]]; then local k="${BASH_REMATCH[1]}" if [[ -n "${HOST_MAP[$k]+_}" ]]; then printf '%s' "${HOST_MAP[$k]}" else printf '%s\n' "$line"; fi