diff --git a/lib/bash/file/README.md b/lib/bash/file/README.md index 212b12b..db230b2 100644 --- a/lib/bash/file/README.md +++ b/lib/bash/file/README.md @@ -25,7 +25,8 @@ update_file_section ~/.bash_profile "# BEGIN APP" "# END APP" \ ## Behavior Notes - Returns success when the target file does not exist and there is nothing to remove. -- Replaces only the first matching marked section when markers already exist. +- Replaces or removes only the first matching marked section when markers already exist. +- Treats markers as exact full lines; marker text embedded in longer lines is ignored. - Appends the marked block when markers are not present. ## Tests diff --git a/lib/bash/file/lib_file.sh b/lib/bash/file/lib_file.sh index fe11515..4b99583 100644 --- a/lib/bash/file/lib_file.sh +++ b/lib/bash/file/lib_file.sh @@ -70,9 +70,9 @@ __file_section_markers_ordered__() { # # This function can add, update, or remove a section of text in a file. # It is designed to be safe to run multiple times. If the section already -# exists, it will be replaced. If it doesn't exist, it will be appended. -# If the target file itself does not exist, this returns success without -# creating the file. +# exists, the first full-line marker pair will be replaced. If it doesn't +# exist, it will be appended. If the target file itself does not exist, this +# returns success without creating the file. # # Usage: # update_file_section [options] [content_lines...] @@ -133,8 +133,8 @@ update_file_section() { fi local beginning_marker_count end_marker_count - beginning_marker_count=$(grep -cF -- "$beginning_marker" "$target_file" || true) - end_marker_count=$(grep -cF -- "$end_marker" "$target_file" || true) + beginning_marker_count=$(grep -cxF -- "$beginning_marker" "$target_file" || true) + end_marker_count=$(grep -cxF -- "$end_marker" "$target_file" || true) if ((beginning_marker_count != end_marker_count)); then log_error "Asymmetric markers in '$target_file': $beginning_marker_count start, $end_marker_count end. Manual repair needed." return 1 @@ -232,9 +232,16 @@ update_file_section() { if [[ "$section_exists" == true ]]; then if [[ "$remove_section" == true ]]; then if awk -v START_M="$beginning_marker" -v END_M="$end_marker" ' - BEGIN { in_section = 0 } - $0 == START_M { in_section = 1; next } - $0 == END_M { in_section = 0; next } + BEGIN { + in_section = 0 + processed = 0 + } + $0 == START_M && processed == 0 { in_section = 1; next } + $0 == END_M && in_section == 1 { + in_section = 0 + processed = 1 + next + } { if (in_section == 0) { print $0 diff --git a/lib/bash/file/tests/lib_file.bats b/lib/bash/file/tests/lib_file.bats index b62a113..e085866 100644 --- a/lib/bash/file/tests/lib_file.bats +++ b/lib/bash/file/tests/lib_file.bats @@ -100,6 +100,21 @@ EOF [ "$(cat "$target")" = $'before\n# BEGIN\nnew\n# END\nafter' ] } +@test "update_file_section ignores marker substrings embedded in longer lines" { + local target="$TEST_TMPDIR/config.txt" + cat <<'EOF' > "$target" +before +echo # BEGIN +old +echo # END +after +EOF + + update_file_section "$target" "# BEGIN" "# END" "new" + + [ "$(cat "$target")" = $'before\necho # BEGIN\nold\necho # END\nafter\n# BEGIN\nnew\n# END' ] +} + @test "update_file_section preserves executable file mode when replacing" { local target="$TEST_TMPDIR/script.sh" cat <<'EOF' > "$target" @@ -201,6 +216,25 @@ EOF [ "$(cat "$target")" = $'before\nafter' ] } +@test "update_file_section removes only the first matching marked block with -r" { + local target="$TEST_TMPDIR/config.txt" + cat <<'EOF' > "$target" +before +# BEGIN +remove-me +# END +middle +# BEGIN +keep-me +# END +after +EOF + + update_file_section -r "$target" "# BEGIN" "# END" + + [ "$(cat "$target")" = $'before\nmiddle\n# BEGIN\nkeep-me\n# END\nafter' ] +} + @test "update_file_section rejects a section with only a start marker" { local target="$TEST_TMPDIR/config.txt" cat <<'EOF' > "$target"