How to Build a Simple Bash HTML Editor in MinutesCreating a simple HTML editor using Bash is a great way to learn shell scripting, text processing, and basic HTML structure. This guide walks you through building a minimal, usable command-line HTML editor that supports creating, opening, editing specific HTML elements, previewing in a browser, and saving changes. No external GUI libraries required — just Bash, common Unix utilities, and a web browser.
Why build a Bash HTML editor?
- Fast setup: Bash scripts run on most Unix-like systems without installing extra packages.
- Educational: You learn shell scripting, file handling, and HTML structure.
- Lightweight: Perfect for quick edits on remote servers or lightweight development environments.
What this editor will do
- Create a new HTML file from a template.
- Open an existing HTML file.
- List editable elements (title, headings, paragraphs, links) and let the user pick one to edit.
- Insert new elements.
- Preview the HTML file in the default web browser.
- Save and quit.
Prerequisites
- Bash (v4+ recommended for associative arrays).
- Standard Unix utilities: sed, awk, grep, sleep, printf, mktemp, xdg-open (Linux) or open (macOS).
- A terminal text editor (nano, vi) is optional but not required.
Script overview
The editor script will:
- Provide a menu-driven interface.
- Use basic parsing with grep/sed to find elements (title, h1–h6, p, a).
- Allow editing an element inline or open it in $EDITOR.
- Save changes back to the file safely (write to temp file, then move).
- Offer a preview option that opens the file in the system default browser.
The script
Copy this into a file named bash-html-editor.sh and make it executable (chmod +x bash-html-editor.sh).
#!/usr/bin/env bash # Simple Bash HTML Editor set -euo pipefail FILE="${1:-}" EDITOR_CMD="${EDITOR:-nano}" TMP="$(mktemp --tmpdir bash_html_edit.XXXXXX.html)" cleanup() { rm -f "$TMP" } trap cleanup EXIT usage() { cat <<EOF Usage: $0 [file.html] If no file is provided, you'll be prompted to create one. EOF exit 1 } new_template() { cat > "$1" <<'HTML' <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Untitled</title> </head> <body> <h1>Welcome</h1> <p>Edit this paragraph.</p> </body> </html> HTML } ensure_file() { if [[ -z "$FILE" ]]; then read -rp "Create new file name (e.g. index.html): " FILE fi if [[ ! -e "$FILE" ]]; then new_template "$FILE" echo "Created $FILE from template." fi } list_elements() { # Print elements with numeric index awk ' BEGIN{idx=0} /<title[^>]*>/ { gsub(/.*<title[^>]*>|</title>.*/, ""); print ++idx ": title: " $0; next } /<h[1-6][^>]*>/ { match($0, /<h[1-6][^>]*>(.*)</h[1-6]>/, m); if(m[1]!="") print ++idx ": heading: " m[1]; next } /<p[^>]*>/ { match($0, /<p[^>]*>(.*)</p>/, m); if(m[1]!="") print ++idx ": paragraph: " m[1]; next } /<a [^>]*>/ { match($0, /<a[^>]*>(.*)</a>/, m); if(m[1]!="") print ++idx ": link: " m[1]; next } ' "$FILE" } element_locations() { # Create a list of line numbers and simple ids for editable elements awk ' BEGIN{idx=0} { line=$0 if(match(line, /<title[^>]*>/)) { if(match(line, /<title[^>]*>(.*)</title>/, m)) { print ++idx ":title:" NR ":" m[1] } } else if(match(line, /<h[1-6][^>]*>/)) { if(match(line, /<(h[1-6])[^>]*>(.*)</>/, m)) { print ++idx ":heading:" NR ":" m[2] } } else if(match(line, /<p[^>]*>/)) { if(match(line, /<p[^>]*>(.*)</p>/, m)) { print ++idx ":paragraph:" NR ":" m[1] } } else if(match(line, /<a [^>]*>/)) { if(match(line, /<a[^>]*>(.*)</a>/, m)) { print ++idx ":link:" NR ":" m[1] } } } ' "$FILE" } edit_line_inline() { local lineno="$1" local before after current new before="$(sed -n "$lineno p" "$FILE")" echo "Current: $before" read -rp "New content (enter the inner HTML to replace): " new # Replace inner HTML between tags # Use perl for safer regex multi-char handling perl -0777 -pe " s{(<[^>]+>)[^<]*(</[^>]+>)}{$1${new//\/\\}${2}} if $. == $lineno; " "$FILE" > "$TMP" && mv "$TMP" "$FILE" echo "Updated line $lineno." } edit_with_editor() { cp "$FILE" "$TMP" "$EDITOR_CMD" "$TMP" mv "$TMP" "$FILE" echo "Saved changes from $EDITOR_CMD." } insert_element() { echo "Choose element to insert:" select el in "h1" "h2" "h3" "p" "a" "Cancel"; do case $el in h1|h2|h3|p) read -rp "Enter text: " txt sed -n '$p' "$FILE" >/dev/null 2>&1 # Insert before closing body tag awk -v tag="$el" -v txt="$txt" '{ if(tolower($0) ~ /</body>/ && !inserted) { print " <"tag">"txt"</"tag">" inserted=1 } print }' "$FILE" > "$TMP" && mv "$TMP" "$FILE" echo "Inserted <$el>." break ;; a) read -rp "Enter href: " href read -rp "Enter link text: " txt awk -v href="$href" -v txt="$txt" '{ if(tolower($0) ~ /</body>/ && !inserted) { print " <a href=""href"">"txt"</a>" inserted=1 } print }' "$FILE" > "$TMP" && mv "$TMP" "$FILE" echo "Inserted <a>." break ;; Cancel) break ;; esac done } preview() { if command -v xdg-open >/dev/null 2>&1; then xdg-open "$FILE" >/dev/null 2>&1 & elif command -v open >/dev/null 2>&1; then open "$FILE" >/dev/null 2>&1 & else echo "No browser opener found. File is at $FILE" fi } main_menu() { while true; do echo echo "File: $FILE" echo "1) List editable elements" echo "2) Edit an element inline" echo "3) Edit full file in $EDITOR ($EDITOR_CMD)" echo "4) Insert element" echo "5) Preview in browser" echo "6) Save and exit" echo "7) Exit without saving" read -rp "Choice: " c case "$c" in 1) element_locations || echo "No editable elements found." ;; 2) echo "Select element number to edit:" element_locations read -rp "Element #: " n loc=$(element_locations | awk -F: -v num="$n" '$1==num{print $0}') if [[ -z "$loc" ]]; then echo "Invalid selection."; continue; fi lineno=$(echo "$loc" | awk -F: '{print $3}') edit_line_inline "$lineno" ;; 3) edit_with_editor ;; 4) insert_element ;; 5) preview ;; 6) echo "Saved to $FILE"; break ;; 7) echo "Aborted. No further changes saved."; exit 0 ;; *) echo "Invalid choice." ;; esac done } # Run ensure_file main_menu
How it works (brief)
- element_locations and list_elements use awk to find simple single-line elements. This editor is minimal and best for small static files where elements are on one line.
- edit_line_inline replaces inner HTML of a specific line using perl to avoid sed’s multi-char regex issues.
- insert_element appends new elements before
Leave a Reply