bash utility

edits.sh

Pull remote files over SFTP, edit them in your local environment, and push changes back automatically on save.

What it does

edits.sh connects to a remote server via SFTP, pulls files into a local temp directory, and opens them in your preferred editor. When you save and close a file, it is pushed back to the remote. Files closed without saving are skipped. The temp directory is cleaned up on exit.

edits.sh means you use your prefered editor with your personal configurations: your keybindings, themes, plugins, LSP, and snippets. It is a lightweight alternative to configuring a full remote development environment for quick edits on servers you don't control or connect to very often.

Usage

01Download the script and make it executable: chmod +x edits.sh
02Optionally install it globally: sudo cp edits.sh /usr/local/bin/edits
03Run it with a remote path: edits user@host:/path/to/dir to edit a directory, or edits user@host:/path/file.txt to target a single file. Run with no argument to be prompted.
04Edit each file in your local editor. Save and close to upload. Close without saving to skip. All changed files are pushed in a single SFTP session.

Flags

-e ext Extension(s) to pull and edit. Defaults to txt. Pass a comma-separated list to match multiple types: -e sh,py,conf.
-r Recurse into subdirectories. Pulls matching files from the full directory tree under the given remote path.
-y Skip the confirmation prompt and begin editing immediately.
$EDITOR Respects the $EDITOR environment variable. Defaults to nano if unset.

Source

edits.sh
#!/usr/bin/env bash
# edits.sh - pull remote files, edit locally, push back on save
# chmod +x edits.sh - make script executable
# sudo cp edits.sh /usr/local/bin/edits
# credit - barney matthews (www.barney.me)

set -euo pipefail

EDITOR="${EDITOR:-nano}"
EXTS="txt"
RECURSIVE=false
YES=false

# Parse flags
while [[ $# -gt 0 ]]; do
  case "$1" in
    -e) EXTS="$2"; shift 2 ;;
    -r) RECURSIVE=true; shift ;;
    -y) YES=true; shift ;;
    *)  REMOTE="$1"; shift ;;
  esac
done

echo ""
echo "edits.sh  [-e ext[,ext]] [-r] [-y] [user@host:/path]"
echo "  -e ext  Extension(s) to edit, comma-separated (default: txt)"
echo "  -r      Recurse into subdirectories"
echo "  -y      Skip confirmation prompt"
echo "  Save and close a file to upload it. Close without saving to skip."
echo ""

# Prompt if no remote provided
if [[ -z "${REMOTE:-}" ]]; then
  read -rp "Remote path (user@host:/path): " REMOTE
fi

REMOTE_HOST="${REMOTE%%:*}"
REMOTE_PATH="${REMOTE#*:}"

TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT

# Detect single-file vs directory target
if [[ "$REMOTE_PATH" == *.* ]] && sftp -q "$REMOTE_HOST" <<SFTP 2>/dev/null
lcd "$TMPDIR"
get "$REMOTE_PATH"
SFTP
then
  FILES=("$TMPDIR"/"$(basename "$REMOTE_PATH")")
else
  # Build sftp mget commands for each extension
  SFTP_CMDS="lcd \"$TMPDIR\"\ncd \"$REMOTE_PATH\""
  IFS=',' read -ra EXT_LIST <<< "$EXTS"
  for ext in "${EXT_LIST[@]}"; do
    ext="${ext// /}"
    if [[ "$RECURSIVE" == true ]]; then
      SFTP_CMDS+="\nfind . -name \"*.$ext\" | while read f; do get \"\$f\"; done"
    else
      SFTP_CMDS+="\nmget *.$ext"
    fi
  done
  sftp -q "$REMOTE_HOST" <<< "$(echo -e "$SFTP_CMDS")" || true

  # Collect all pulled files
  FILES=()
  for ext in "${EXT_LIST[@]}"; do
    ext="${ext// /}"
    while IFS= read -r f; do
      FILES+=("$f")
    done < <(find "$TMPDIR" -name "*.$ext" -type f)
  done
fi

if [[ ${#FILES[@]} -eq 0 ]]; then
  echo "No matching files found." >&2
  exit 1
fi

echo "Found ${#FILES[@]} file(s): $(basename -a "${FILES[@]}" | tr '\n' ' ')"
echo ""

# Edit each file; track which ones changed
CHANGED=()
for f in "${FILES[@]}"; do
  MTIME_BEFORE="$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f")"
  "$EDITOR" "$f"
  MTIME_AFTER="$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f")"
  if [[ "$MTIME_BEFORE" != "$MTIME_AFTER" ]]; then
    CHANGED+=("$f")
  else
    echo "No changes to $(basename "$f"), skipping."
  fi
done

# Batch upload all changed files in one SFTP session
if [[ ${#CHANGED[@]} -gt 0 ]]; then
  echo ""
  echo "Uploading ${#CHANGED[@]} file(s) ..."
  UPLOAD_CMDS="cd \"$REMOTE_PATH\""
  for f in "${CHANGED[@]}"; do
    UPLOAD_CMDS+="\nput \"$f\""
  done
  sftp -q "$REMOTE_HOST" <<< "$(echo -e "$UPLOAD_CMDS")"
  echo "Done."
fi