How to Create a Bash File Editing Tool

How to Create a Bash File Editing Tool

How to Create a Bash File Editing Tool

Interactive editors like Vim and Nano are great for hands-on work, but a lot of real editing needs are actually programmatic: replace this string across a hundred files, insert a line after every match of a pattern, delete a specific line number, comment out a block. Doing that by hand doesn’t scale. A Bash file editing tool built around sed, awk, and careful in-place handling covers this gap completely.

This article builds a general-purpose editing utility supporting find-and-replace, line insertion/deletion, and safe batch editing across many files at once.

Core Editing Operations

A practical editing tool needs to support at minimum:

  1. Find and replace a string or pattern.
  2. Insert a line at a specific position or after a pattern match.
  3. Delete a line by number or pattern match.
  4. Append/prepend content to a file.

Each of these maps cleanly onto sed, with awk as a fallback for more complex logic.

Step 1: Find and Replace

#!/usr/bin/env bash
set -euo pipefail

FILE="$1"
FIND="$2"
REPLACE="$3"

cp "$FILE" "${FILE}.bak"

sed -i "s/${FIND//\//\\/}/${REPLACE//\//\\/}/g" "$FILE"

echo "Replaced '$FIND' with '$REPLACE' in $FILE (backup: ${FILE}.bak)"

How It Works

Example:

./edit.sh config.txt "localhost" "127.0.0.1"

Step 2: Using a Different Delimiter for Paths

Search-and-replace involving file paths (which contain /) gets awkward with the default delimiter. Using an alternate delimiter like | avoids the escaping problem entirely:

sed -i "s|${FIND}|${REPLACE}|g" "$FILE"

This is simpler and more readable whenever the search or replace text is likely to contain slashes, such as paths or URLs.

Step 3: Inserting a Line

Inserting after a specific line number:

insert_after_line() {
    local file="$1" line_num="$2" text="$3"
    sed -i "${line_num}a\\${text}" "$file"
}

Inserting after a pattern match:

insert_after_pattern() {
    local file="$1" pattern="$2" text="$3"
    sed -i "/${pattern}/a\\${text}" "$file"
}

Step 4: Deleting Lines

By line number:

sed -i "${LINE_NUM}d" "$FILE"

By pattern match:

sed -i "/${PATTERN}/d" "$FILE"

By line range:

sed -i "${START},${END}d" "$FILE"

Each variant addresses sed‘s d (delete) command differently — a single line number, a pattern, or an inclusive numeric range.

Step 5: Appending and Prepending Content

append_to_file() {
    local file="$1" text="$2"
    echo "$text" >> "$file"
}

prepend_to_file() {
    local file="$1" text="$2"
    echo "$text" | cat - "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
}

Appending is trivial with >>. Prepending requires more care: there’s no native “insert at the top” redirect, so the new content is piped through cat - (which reads stdin first) followed by the original file, writing the combined result to a temp file before replacing the original.

Step 6: A Unified Editing Script with Subcommands

#!/usr/bin/env bash
set -euo pipefail

usage() {
    cat <<EOF
Usage: $0 <command> <file> [args]

Commands:
  replace <file> <find> <replace>
  insert-after-line <file> <line_num> <text>
  insert-after-pattern <file> <pattern> <text>
  delete-line <file> <line_num>
  delete-pattern <file> <pattern>
  append <file> <text>
  prepend <file> <text>
EOF
    exit 1
}

CMD="${1:-}"
[[ -z "$CMD" ]] && usage
FILE="${2:-}"
[[ -z "$FILE" || ! -f "$FILE" ]] && { echo "File not found: $FILE"; exit 1; }

cp "$FILE" "${FILE}.bak"

case "$CMD" in
  replace)
    FIND="$3"; REPLACE="$4"
    sed -i "s|${FIND}|${REPLACE}|g" "$FILE"
    ;;
  insert-after-line)
    LINE_NUM="$3"; TEXT="$4"
    sed -i "${LINE_NUM}a\\${TEXT}" "$FILE"
    ;;
  insert-after-pattern)
    PATTERN="$3"; TEXT="$4"
    sed -i "/${PATTERN}/a\\${TEXT}" "$FILE"
    ;;
  delete-line)
    LINE_NUM="$3"
    sed -i "${LINE_NUM}d" "$FILE"
    ;;
  delete-pattern)
    PATTERN="$3"
    sed -i "/${PATTERN}/d" "$FILE"
    ;;
  append)
    TEXT="$3"
    echo "$TEXT" >> "$FILE"
    ;;
  prepend)
    TEXT="$3"
    echo "$TEXT" | cat - "$FILE" > "${FILE}.tmp" && mv "${FILE}.tmp" "$FILE"
    ;;
  *)
    usage
    ;;
esac

echo "Done: $CMD applied to $FILE (backup: ${FILE}.bak)"

Example usage:

./edit.sh replace app.conf "debug=false" "debug=true"
./edit.sh insert-after-pattern app.conf "^\[server\]" "timeout=30"
./edit.sh delete-pattern app.conf "^#.*deprecated"

Step 7: Batch Editing Across Multiple Files

Wrapping the above into a batch loop extends it to entire directories:

#!/usr/bin/env bash
set -euo pipefail

DIR="$1"
FIND="$2"
REPLACE="$3"
PATTERN_GLOB="${4:-*}"

find "$DIR" -type f -name "$PATTERN_GLOB" | while read -r file; do
    cp "$file" "${file}.bak"
    sed -i "s|${FIND}|${REPLACE}|g" "$file"
    echo "Updated: $file"
done
./batch-edit.sh ./src "old-api.example.com" "new-api.example.com" "*.js"

This finds every .js file under ./src and applies the same replacement, backing up each file individually before modifying it.

Real-World Use Cases

Automation Example: Version Bump Across Files

#!/usr/bin/env bash
set -euo pipefail

OLD_VERSION="$1"
NEW_VERSION="$2"

for file in package.json setup.py Cargo.toml VERSION; do
    [[ -f "$file" ]] || continue
    ./edit.sh replace "$file" "$OLD_VERSION" "$NEW_VERSION"
done

echo "Version bumped from $OLD_VERSION to $NEW_VERSION"

This is a common release-automation pattern: bump a version string consistently across every file that declares it, in one script invocation.

Best Practices

Security Considerations

Optimization Tips

Troubleshooting

Common Mistakes

FAQs

Can this tool do a “dry run” to preview changes before applying them? Yes — swap sed -i for plain sed (without -i) to print the result to stdout without modifying the file, letting you review the diff first: diff <(sed 's|find|replace|g' file) file.

Does this handle multi-line replacements? Basic sed operates line-by-line by default; multi-line pattern matching requires more advanced sed scripting (the “hold space” pattern) or switching to perl -0777 -pe for cross-line regex support.

What’s the difference between using sed here versus awk? sed is ideal for line-oriented substitution and deletion. awk is better suited when the edit logic depends on field-based parsing (columns) or requires more complex conditional logic per line.

Summary

A general-purpose Bash editing tool built on sed covers the overwhelming majority of programmatic text-editing needs: substitution, insertion, deletion, and content prepending/appending — all with safe backup-before-edit discipline. Wrapped in subcommands and extended to batch operation across directories, it turns what would otherwise be one-off, error-prone manual edits into a repeatable, auditable process.

References

Exit mobile version