An open API service indexing awesome lists of open source software.

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

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