https://github.com/agzam/mxp
Shell script for piping things in and out of Emacs buffers
https://github.com/agzam/mxp
emacs pipe terminal
Last synced: 4 days ago
JSON representation
Shell script for piping things in and out of Emacs buffers
- Host: GitHub
- URL: https://github.com/agzam/mxp
- Owner: agzam
- License: mit
- Created: 2025-10-29T23:48:20.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2026-04-06T19:40:43.000Z (3 months ago)
- Last Synced: 2026-04-06T21:15:09.122Z (3 months ago)
- Topics: emacs, pipe, terminal
- Language: Shell
- Homepage: https://raw.githubusercontent.com/agzam/mxp/main/mxp
- Size: 73.2 KB
- Stars: 28
- Watchers: 0
- Forks: 2
- Open Issues: 2
-
Metadata Files:
- Readme: README.org
- Changelog: changelog.org
- License: LICENSE
Awesome Lists containing this project
README
#+TITLE: mxp AKA "Emacs Piper"
#+AUTHOR: Ag Ibragimov
#+DATE: 2025
#+OPTIONS: toc:t
[[https://github.com/agzam/mxp/actions/workflows/test.yml][https://github.com/agzam/mxp/actions/workflows/test.yml/badge.svg]]
Pipe content between your terminal and Emacs buffers - seamlessly bridge your command-line workflows with your editor.
[[https://www.youtube.com/watch?v=LVlF3-KyqvY][Demo on YouTube, ~12min]]
* What?
~mxp~ is a shell script that acts as a bridge between Unix pipes and Emacs buffers. It supports:
- *Write mode*: Pipe stdin to Emacs buffers (creates new or appends to existing)
- *Read mode*: Output buffer content to stdout for piping
- *Streaming*: Real-time content updates with chunked processing
- *Buffer matching*: Use exact names or regex patterns to find buffers
- *Auto-generation*: Creates ~*Piper 1*~, ~*Piper 2*~, etc. when no name specified
- *Conflict handling*: Avoids overwriting buffers unless forced
- *Persistent socket*: Auto-boots a TCP eval server inside Emacs for fast, ordered communication (falls back to ~emacsclient~ transparently)
The script works with both bash and zsh, handles special characters properly, and includes comprehensive error handling.
** Do not confuse it with similarly named emacs-piper
Howard Abram's [[https://gitlab.com/howardabrams/emacs-piper][emacs-piper]] project although has similarities, it works differently and has different goals.
* Why?
- Pipe command output directly into Emacs buffers for comfortable viewing
- Stream logs in real-time to watch them in your editor
- Extract buffer content from Emacs and pipe it to terminal commands
*** Did you know about piping in Eshell?
Emacs has piping in/out buffers in Eshell! You can pipe content into a buffer like this:
#+begin_src emacs-lisp
;; In eshell, you can pipe command output to a buffer
;; You can press to pick a buffer instead of typing it
cat file.txt > #
#+end_src
Unfortunately, Eshell doesn't support input redirection, so reading from a buffer ain't so straightforward, yet still possible. You just need to create a custom eshell command to read from a buffer, e.g.:
#+begin_src emacs-lisp
;; note the eshell/ prefix
(defun eshell/b (buf-or-regexp)
"Output buffer content of buffer matching BUF-OR-REGEXP."
(let ((buf (if (bufferp buf-or-regexp)
buf-or-regexp
(cl-loop for b in (buffer-list)
thereis (and (string-match-p
buf-or-regexp (buffer-name b))
b)))))
(when buf
(with-current-buffer buf
(buffer-substring-no-properties (point-min) (point-max))))))
#+end_src
And then we can use it in Eshell:
#+begin_src emacs-lisp
;; Press to pick a buffer instead of typing it
b # | rg "error"
#+end_src
Incredibly powerful, but this only works *inside* Eshell - you cannot use it in an external terminal. ~mxp~ brings this same workflow to your regular shell, letting you pipe between *any* terminal and Emacs.
* How?
** Make sure you have:
- Emacs with ~emacsclient~ ([[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html][Emacs daemon]] must be running)
- bash >= 4.0 or zsh
- Standard Unix utilities (~base64~, ~grep~, ~sed~)
** Install
Download and put it somewhere in the $PATH
#+begin_src shell
curl -fsSL https://raw.githubusercontent.com/agzam/mxp/refs/heads/main/mxp -o \
~/.local/bin/mxp \
&& chmod +x ~/.local/bin/mxp
#+end_src
Make sure [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html][Emacs daemon]] is running and emacsclient works!
** Socket transport
On first use, ~mxp~ boots a lightweight TCP eval server inside your running Emacs and connects to it directly via bash's ~/dev/tcp~. All subsequent calls reuse this persistent connection, which means:
- No process spawning per eval (previously each chunk launched a new ~emacsclient~ process)
- Guaranteed ordering for streamed content (no more background job races)
- No "Connection refused" errors under heavy streaming load
The server starts automatically - no manual ~M-x~ step required. If the socket is unavailable (e.g., bash built without ~/dev/tcp~ support), ~mxp~ falls back to ~emacsclient~ transparently.
#+attr_html: :width 50%
| Variable | Default | Description |
|-------------------+---------+-----------------------------------------------------|
| ~MXP_PORT~ | ~17394~ | TCP port for the eval server (localhost only) |
| ~MXP_NO_SOCKET~ | (unset) | Set to ~1~ to force ~emacsclient~-only mode |
| ~EMACS_SOCKET_NAME~ | (unset) | Named socket for ~emacsclient -s~ (e.g., daemon name) |
You can also pass ~-s~ / ~--socket-name~ on the command line:
#+begin_src shell
echo "hello" | mxp -s myserver "*my-buffer*"
mxp --socket-name myserver --from "*my-buffer*"
#+end_src
This is useful when the Emacs daemon was started with a specific socket name, e.g., ~emacs --daemon=myserver~.
To stop the server manually:
#+begin_src emacs-lisp
(when (and (boundp 'mxp-server-process) (process-live-p mxp-server-process))
(delete-process mxp-server-process)
(setq mxp-server-process nil))
#+end_src
** Use
*** Open files and directories in Emacs
#+begin_src shell
# Open a file
mxp my-file.txt
mxp README.org
# Open current directory in dired
mxp .
# Open any directory
mxp ~/Projects
mxp /path/to/directory
# Works with relative paths
mxp ../other-project/file.txt
#+end_src
*** Pipe command output into Emacs
#+begin_src shell
# Pipe to a named buffer
cat file.txt | mxp "my-buffer"
# Pipe to auto-generated buffer (*Piper 1*, *Piper 2*, etc.)
tail -f /var/log/app.log | mxp
# Append to existing buffer
echo "more content" | mxp --append "my-buffer"
echo "more content" | mxp -a "my-buffer"
# Prepend to existing buffer (insert at the top)
echo "header info" | mxp --prepend "my-buffer"
echo "header info" | mxp -p "my-buffer"
# Match buffer by regex
echo "data" | mxp "mybuf.*"
# Force overwrite existing buffer
cat new.txt | mxp --force "my-buffer"
cat new.txt | mxp -F "my-buffer"
#+end_src
*** Extract buffer content and pipe to commands
#+begin_src shell
# Output buffer to stdout
mxp --from "my-buffer"
mxp -f "my-buffer"
# Pipe buffer to commands
mxp --from "*Messages*" | grep error
mxp -f ".*scratch.*" | wc -l
# Use in command chains
mxp -f "my-buffer" | sort | uniq | less
#+end_src
*** Process substitution
Works naturally with process substitution for commands expecting files:
#+begin_src shell
# Compare two buffers
diff <(mxp -f "version-1") <(mxp -f "version-2")
# Use buffer as input file
jq . <(mxp -f "*json-data*")
#+end_src
** Emacs Hooks
There are hooks that you can customize:
#+HTML:
#+HTML: RunsArgsNotes
#+HTML:
mxp-buffer-hook
when the buffer appearsBUFFER-NAMEuseful for setting major mode, etc.
#+HTML: mxp-buffer-update-hook
whenever there's more dataBUFFER-NAME
BEG-POS,END-POS
where buffer gets updated
#+HTML: mxp-buffer-complete-hook
at the completionBUFFER-NAMEmay never run for continuous streams
#+HTML:
Hook examples:
#+begin_src emacs-lisp
(defun on-mxp-buffer-h (buffer-name)
(with-current-buffer buffer-name
(when (string-match ".*\\.json.*" buffer-name)
(json-mode))))
(add-hook 'mxp-buffer-hook #'on-mxp-buffer-h)
;; This is how you can re-apply colors. I don't want to make escape
;; color code processing built into the script itself. It's better to
;; keep that customizable.
(defun on-mxp-buffer-update-h (buffer-name beg end)
(with-current-buffer buffer-name
(ansi-color-apply-on-region beg end)))
(add-hook 'mxp-buffer-update-hook #'on-mxp-buffer-update-h)
(defun on-mxp-buffer-complete-h (buffer-name)
(with-current-buffer buffer-name
;; delete all empty lines
(flush-lines "^$" (point-min) (point-max))))
(add-hook 'mxp-buffer-complete-hook #'on-mxp-buffer-complete-h)
#+end_src
** Usage examples
#+begin_src shell
# Quick file/directory access
mxp config.json
mxp .
mxp ~/Documents
mxp $HOME
# Watch build logs in Emacs
npm run build | mxp "build-logs"
# Send curl output to Emacs for inspection
curl -s "https://api.thedogapi.com/v1/breeds" | jq | mxp "breeds"
# and the the opposite direction:
mxp "breeds" | jq '.[].name' | sort | mxp "dog names"
# Extract TODO items from buffer
mxp -f "*scratch*" | grep TODO > todos.txt
# Add timestamps to the top of a log buffer
date | mxp --prepend "logs"
tail -f app.log | mxp --append "logs"
# Stitch multiple buffers together
cat <(mxp -f "header") <(mxp -f "body") | mail -s "Report" user@example.com
# Edit a file, then pipe its buffer content through a command
mxp config.yaml # Opens in Emacs
mxp -f config.yaml | yq '.version' - # Read it back
# Stream some data with a passtrhough (shows results in both the buffer and terminal)
ping google.com | tee >(mxp)
#+end_src
** [[file:changelog.org][Changelog]] & [[file:LICENSE][License]]
#+HTML: Copyright © 2025 Ag Ibragimov