Minimalist Bash HTML Editor for Rapid HTML Prototyping

Build a Simple Bash HTML Editor — Edit HTML Files from the TerminalEditing HTML files directly from the terminal can be fast, portable, and empowering—especially when you want a lightweight workflow or need to make quick edits on a remote server. In this article you’ll learn how to build a simple, usable Bash-based HTML editor that opens, edits, previews, and saves HTML files without leaving the shell. The editor will provide basic features: open/create files, search and replace, insert templates, basic navigation, simple line editing, and an in-terminal preview using a terminal browser. It’s not meant to replace full-featured editors (vim, emacs, code editors), but it’s a useful tool for quick fixes, teaching, automation, or constrained environments.


Why build a Bash HTML editor?

  • Lightweight: No GUI, minimal dependencies, runs on most Unix-like systems.
  • Scriptable: Easily integrated into automation, CI, SSH sessions.
  • Educational: Teaches shell scripting, file handling, and simple text manipulation.
  • Portable: Works over SSH and on systems without graphical environments.

Requirements and dependencies

  • POSIX-compatible shell (bash preferred)
  • Standard Unix utilities: sed, awk, grep, cat, printf, read, mv, cp, mkdir, rm
  • Optional for preview: w3m, lynx, or a local headless browser (e.g., w3m is tiny and convenient)
  • Optional for improved editing: nano, sed-based multi-line editing tricks

Install w3m (Debian/Ubuntu):

sudo apt update && sudo apt install -y w3m 

Design overview

The editor will be a single Bash script that:

  1. Loads or creates an HTML file.
  2. Displays a numbered list of lines (or a portion) for context.
  3. Provides commands to insert, delete, replace, view, preview, save, and exit.
  4. Supports search and simple regex replace using sed.
  5. Offers templates/snippets for common HTML boilerplate.

We will implement a command loop that reads user input and dispatches commands. File data will be managed in a temporary working copy to avoid accidental corruption.


The script: full implementation

Save the following as edit-html.sh and make it executable (chmod +x edit-html.sh). The script is commented to explain each part.

#!/usr/bin/env bash # edit-html.sh - Simple Bash HTML editor for terminal use # Usage: ./edit-html.sh [filename] set -euo pipefail IFS=$' 	' # Config TMPDIR="${TMPDIR:-/tmp}" WORKDIR="$(mktemp -d "${TMPDIR}/bash-html-edit.XXXXXX")" CLEANUP_ON_EXIT=true PREVIEWER="${PREVIEWER:-w3m}"   # set to 'lynx' or 'w3m' if available # Cleanup function cleanup() {   if [[ "${CLEANUP_ON_EXIT}" == "true" ]]; then     rm -rf -- "${WORKDIR}"   fi } trap cleanup EXIT # Helper: print usage usage() {   cat <<EOF Usage: $0 [file.html] Simple terminal HTML editor. Commands (type command then Enter):   open <file>      - open or create file   show [start [end]] - show lines start..end (default: 1..40)   insert <line>    - insert text at line (interactive; end with a lone '.' on a line)   append <line>    - append after line (interactive; end with a lone '.' on a line)   replace <line>   - replace a single line interactively   delete <start> [end] - delete line(s)   search <pattern> - grep pattern (basic)   sed <expr>       - run sed expression on file (e.g., 's/foo/bar/g')   template         - insert basic HTML boilerplate   preview          - open in terminal browser (${PREVIEWER})   save [filename]  - save to original or new filename   mv <newname>     - rename current file   export <out>     - write current to out (without changing current file)   help             - show this help   quit|exit        - exit (prompts to save if changed) EOF } # Load initial file CURRENT_FILE="${1:-}" if [[ -n "${CURRENT_FILE}" ]]; then   if [[ -e "${CURRENT_FILE}" ]]; then     cp -- "${CURRENT_FILE}" "${WORKDIR}/buffer.html"   else     touch "${WORKDIR}/buffer.html"   fi else   touch "${WORKDIR}/buffer.html" fi ORIGINAL_NAME="${CURRENT_FILE:-untitled.html}" BUFFER="${WORKDIR}/buffer.html" MODIFIED=false # Functions for editing show_lines() {   local start=${1:-1}   local end=${2:-40}   nl -ba -w4 -s'  ' "${BUFFER}" | sed -n "${start},${end}p" } read_block() {   echo "Enter text; finish with a single dot '.' on its own line."   local tmp="${WORKDIR}/$$.in"   : > "${tmp}"   while IFS= read -r line; do     [[ "${line}" == "." ]] && break     printf '%s ' "${line}" >> "${tmp}"   done   cat "${tmp}" } insert_at() {   local lineno=$1   local tmp="${WORKDIR}/$$.tmp"   local block   block="$(read_block)"   awk -v L="${lineno}" -v blk="${block}" 'BEGIN{split(blk,lines," ");} {print; if(NR==L){for(i in lines)print lines[i]}}' "${BUFFER}" > "${tmp}" && mv "${tmp}" "${BUFFER}"   MODIFIED=true } append_after() {   local lineno=$1   local tmp="${WORKDIR}/$$.tmp"   local block   block="$(read_block)"   awk -v L="${lineno}" -v blk="${block}" 'BEGIN{split(blk,lines," ");} {print; if(NR==L){for(i=1;i<=length(lines);i++)print lines[i]}}' "${BUFFER}" > "${tmp}" && mv "${tmp}" "${BUFFER}"   MODIFIED=true } replace_line() {   local lineno=$1   local tmp="${WORKDIR}/$$.tmp"   echo "Current line:"   sed -n "${lineno}p" "${BUFFER}"   echo "Enter replacement (single line):"   IFS= read -r newline   awk -v L="${lineno}" -v nl="${newline}" 'NR==L{print nl; next} {print}' "${BUFFER}" > "${tmp}" && mv "${tmp}" "${BUFFER}"   MODIFIED=true } delete_range() {   local start=$1   local end=${2:-$1}   local tmp="${WORKDIR}/$$.tmp"   sed "${start},${end}d" "${BUFFER}" > "${tmp}" && mv "${tmp}" "${BUFFER}"   MODIFIED=true } search_pattern() {   grep -n --color=auto -E -- "$1" "${BUFFER}" || true } run_sed() {   local expr="$1"   local tmp="${WORKDIR}/$$.tmp"   sed -E "${expr}" "${BUFFER}" > "${tmp}" && mv "${tmp}" "${BUFFER}"   MODIFIED=true } insert_template() {   cat >"${WORKDIR}/tmpl.html" <<'EOF' <!doctype html> <html lang="en"> <head>   <meta charset="utf-8">   <meta name="viewport" content="width=device-width,initial-scale=1">   <title>New Document</title>   <link rel="stylesheet" href="styles.css"> </head> <body>   <header><h1>Heading</h1></header>   <main>     <p>Your content here.</p>   </main>   <footer><small>© $(date +%Y)</small></footer> </body> </html> EOF   # Insert template at end   cat "${WORKDIR}/tmpl.html" >> "${BUFFER}"   MODIFIED=true } preview_buffer() {   if command -v "${PREVIEWER}" >/dev/null 2>&1; then     "${PREVIEWER}" "${BUFFER}"   else     echo "Previewer '${PREVIEWER}' not found. Install w3m or set PREVIEWER env var."   fi } save_buffer() {   local out="${1:-${ORIGINAL_NAME}}"   cp -- "${BUFFER}" "${out}"   ORIGINAL_NAME="${out}"   MODIFIED=false   echo "Saved to ${out}" } rename_current() {   local newname="$1"   ORIGINAL_NAME="${newname}"   echo "Current name set to ${ORIGINAL_NAME}" } export_to() {   local out="$1"   cp -- "${BUFFER}" "${out}"   echo "Exported to ${out}" } confirm_save_on_exit() {   if [[ "${MODIFIED}" == "true" ]]; then     echo -n "You have unsaved changes. Save before exit? (y/n) "     IFS= read -r ans     if [[ "${ans}" =~ ^[Yy]$ ]]; then       echo -n "Save as (default: ${ORIGINAL_NAME}): "       IFS= read -r fname       if [[ -z "${fname}" ]]; then         save_buffer "${ORIGINAL_NAME}"       else         save_buffer "${fname}"       fi     fi   fi } # Main REPL echo "Simple Bash HTML Editor — editing: ${ORIGINAL_NAME}" usage while true; do   printf ' editor:%s> ' "${ORIGINAL_NAME}"   if ! IFS= read -r cmdline; then     echo     confirm_save_on_exit     exit 0   fi   cmd=( $cmdline )   case "${cmd[0]}" in     open)       if [[ -z "${cmd[1]:-}" ]]; then         echo "Usage: open <file>"       else         cp -- "${cmd[1]}" "${BUFFER}" 2>/dev/null || touch "${BUFFER}"         ORIGINAL_NAME="${cmd[1]}"         MODIFIED=false         echo "Opened ${ORIGINAL_NAME}"       fi       ;;     show)       show_lines "${cmd[1]:-1}" "${cmd[2]:-40}"       ;;     insert)       if [[ -z "${cmd[1]:-}" ]]; then         echo "Usage: insert <line>"       else         insert_at "${cmd[1]}"       fi       ;;     append)       if [[ -z "${cmd[1]:-}" ]]; then         echo "Usage: append <line>"       else         append_after "${cmd[1]}"       fi       ;;     replace)       if [[ -z "${cmd[1]:-}" ]]; then         echo "Usage: replace <line>"       else         replace_line "${cmd[1]}"       fi       ;;     delete)       if [[ -z "${cmd[1]:-}" ]]; then         echo "Usage: delete <start> [end]"       else         delete_range "${cmd[1]}" "${cmd[2]:-}"       fi       ;;     search)       if [[ -z "${cmd[1]:-}" ]]; then         echo "Usage: search <pattern>"       else         search_pattern "${cmdline#* }"       fi       ;;     sed)       if [[ -z "${cmd[1]:-}" ]]; then         echo "Usage: sed <expr>"       else         run_sed "${cmdline#* }"       fi       ;;     template)       insert_template       echo "Inserted HTML template."       ;;     preview)       preview_buffer       ;;     save)       save_buffer "${cmd[1]:-}"       ;;     mv)       if [[ -z "${cmd[1]:-}" ]]; then         echo "Usage: mv <newname>"       else         rename_current "${cmd[1]}"       fi       ;;     export)       if [[ -z "${cmd[1]:-}" ]]; then         echo "Usage: export <out>"       else         export_to "${cmd[1]}"       fi       ;;     help)       usage       ;;     quit|exit)       confirm_save_on_exit       exit 0       ;;     *)       if [[ -n "${cmd[0]}" ]]; then         echo "Unknown command: ${cmd[0]}. Type 'help' for commands."       fi       ;;   esac done 

Usage examples

  • Start editing a file: ./edit-html.sh index.html

  • Show lines 1–60: Type: show 1 60

  • Insert a block after line 10: Type: insert 10 Then paste lines and finish with a line containing just a single dot (.)

  • Replace line 5: Type: replace 5 Then enter the replacement line.

  • Quick search: Type: search “

  • Preview in terminal: Type: preview (requires w3m/lynx)

  • Save: Type: save or save newname.html


Tips and extensions

  • Add syntax highlighting by piping to source-highlight or bat (if available).
  • Integrate with git: auto-commit after save.
  • Add multi-line replace using awk/perl for more robust patterns.
  • Create custom snippets for common components (navbars, cards).
  • Use an external editor: add a command to open \(BUFFER in \)EDITOR (e.g., nano, vim) then reload.

Example to open in $EDITOR and reload:

editor() {   ${EDITOR:-nano} "${BUFFER}"   MODIFIED=true } 

Limitations

  • Not a full-featured editor — lacks undo stack, smart indentation, and advanced search/replace.
  • Designed for small-to-medium files; very large files may be slow.
  • Editing UX is basic; for heavy editing use vim/nano/emacs/VS Code.

Conclusion

This simple Bash HTML editor gives you the ability to create and edit HTML files directly from the terminal with minimal dependencies. It’s a practical utility for quick fixes, remote work over SSH, and learning shell scripting. Customize it with snippets, external editors, or more advanced parsing as your needs grow.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *