Pipe content between your terminal and Emacs buffers - seamlessly bridge your command-line workflows with your editor.
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
emacsclienttransparently)
The script works with both bash and zsh, handles special characters properly, and includes comprehensive error handling.
Howard Abram’s emacs-piper project although has similarities, it works differently and has different goals.
- 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
Emacs has piping in/out buffers in Eshell! You can pipe content into a buffer like this:
;; In eshell, you can pipe command output to a buffer
;; You can press <C-c M-b> to pick a buffer instead of typing it
cat file.txt > #<buffer some-buffer> 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.:
;; 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))))))And then we can use it in Eshell:
;; Press <C-c M-b> to pick a buffer instead of typing it
b #<buffer README.org> | rg "error"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.
- Emacs with
emacsclient(Emacs daemon must be running) - bash >= 4.0 or zsh
- Standard Unix utilities (
base64,grep,sed)
Download and put it somewhere in the $PATH
curl -fsSL https://raw.githubusercontent.com/agzam/mxp/refs/heads/main/mxp -o \
~/.local/bin/mxp \
&& chmod +x ~/.local/bin/mxp Make sure Emacs daemon is running and emacsclient works!
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
emacsclientprocess) - 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.
| 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 |
To stop the server manually:
(when (and (boundp 'mxp-server-process) (process-live-p mxp-server-process))
(delete-process mxp-server-process)
(setq mxp-server-process nil))# 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# 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"# 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 | lessWorks naturally with process substitution for commands expecting files:
# Compare two buffers
diff <(mxp -f "version-1") <(mxp -f "version-2")
# Use buffer as input file
jq . <(mxp -f "*json-data*")There are hooks that you can customize:
| Runs | Args | Notes | |
|---|---|---|---|
mxp-buffer-hook | when the buffer appears | BUFFER-NAME | useful for setting major mode, etc. |
mxp-buffer-update-hook | whenever there’s more data | BUFFER-NAMEBEG-POS,END-POSwhere buffer gets updated | |
mxp-buffer-complete-hook | at the completion | BUFFER-NAME | may never run for continuous streams |
Hook examples:
(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)# 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)