0

Given lines like these:

pwd

echo "Hello \
World"

in a buffer, I would like to do the following:

  1. Put the cursor to the first line, then press C-`
  2. Select lines 3-4 then press C-`

to send the line or selection to a shell bash and insert the output in the following line. Ideally, the terminal should be persistent, thus setting variables then reusing them is possible.

I found in Evil mode, I could type !!sh to do 1. and !sh when a region is selected (however this replaces the command with the returned output), but couldn't figure out how to map C-` to !!sh. :.w !sh seems to be closer to what I wanted.

There seems to be an Emacs package run-stuff that offers similar functionality, but I'd rather use ways provided by Vanilla Emacs plus common Emacs packages such as evil-mode, use-package etc.

Ultimately, I'd like to embed an acme-like workflow into Emacs, where I can put a few short commands on an anchored top line, then run them while providing a selected text as input to the said command, and if possible, some other selected text (maybe using multiselect) as options to the command.

1 Answer 1

4

M-| (M-x shell-command-on-region) runs a shell command using the region as stdin, display the output in the echo area, with a prefix arg, replace the region with the output.

Unlike your shell in a terminal, each use of M-! or M-|: start a shell such as bash, run the shell command, then kill the shell. And the region is data passed to the shell command, the content is arbitrary, for example,

ABCDE             M-| wc
{"type": "JSON"}  M-| jq

the above works because wc and jq read stdin as input, and since the executables bash, ruby and python3 read stdin as corresponding programming code, you can run Bash/Ruby/Python code with M-|, e.g.,

echo "Bash"       M-| bash
puts "Ruby"       M-| ruby
print("Python")   M-| python3

In other words, M-| is simple and generic, so stick with it, if you need insert the output, simply copy-and-paste, though defining your own command isn't hard at all, e.g.,

(defun your-shell-command-on-region ()
  "Like `shell-command-on-region' but insert output after the region."
  (declare (interactive-only t))
  (interactive)
  (let ((output-buffer (generate-new-buffer " *temp*")))
    (shell-command-on-region
     (region-beginning) (region-end)
     (read-shell-command "Shell command on region: ")
     output-buffer)
    (goto-char (region-end))
    (unless (bolp)
      (insert ?\n))
    (insert-buffer-substring output-buffer)
    (kill-buffer output-buffer)))

Run current line or selection in shell then insert result in buffer in Emacs

Let's assuming the current line or selection are shell code

(defun $ (b e)
  "Run current line or selection in shell and insert output."
  (interactive
   (if (use-region-p)
       (list (region-beginning) (region-end))
     (list (line-beginning-position) (line-end-position))))
  (save-excursion
    (goto-char e)
    (unless (bolp) (insert "\n"))
    (shell-command (buffer-substring-no-properties b e) t t)))

(global-set-key (kbd "C-c C-c") #'$)

A cool idea is update the output in-place (like Org Mode Babel), that is, delete the old output then insert the new output,

(defun $$ (b e)
  "Run current line as shell code and insert/update output."
  (interactive (list (line-beginning-position)
                     (line-end-position)))
  (save-excursion
    ;; delete old output
    (delete-region
     (progn (forward-line) (point))
     (progn (while (get-text-property (point) '$$)
              (forward-line))
            (point)))

    (unless (bolp) (insert "\n"))
    (let* ((command (buffer-substring-no-properties b e))
           (output (with-temp-buffer
                     (shell-command command t t)
                     (buffer-string)))
           (start (point)))
      (insert (propertize output '$$ t 'rear-nonsticky t))
      (pulse-momentary-highlight-region start (point)))))

Try it with date.

Ultimately, I'd like to embed an acme-like workflow into Emacs, where I can put a few short commands on an anchored top line, then run them while providing a selected text as input to the said command,

M-| does this, though the user interface is different, the input is from the region, the command is from the minibuffer, the output is to echo area.

To implement the similar workflow you described, get started with three buffers for input/command/output, the code should be straightforward.

2
  • Thanks! My elisp-Fu isn't strong enough yet to implement something like your $$. That was very cool! C-c C-c is bound in org-mode, so I am binding it to C-` . I tested $$ on a line ls ~/Desktop but it failed; I wonder if we can treat ~ or anything in the line as string literals.
    – hyiltiz
    Feb 13, 2020 at 21:14
  • @hyiltiz It should not fail, behind the scenes, it's simply M-! ls ~/Desktop, you obviously want ~ to be expand into your home directory and shell does it for you. You can prevent the expansion via quote, e.g., echo '~/Desktop'.
    – xuchunyang
    Feb 13, 2020 at 22:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.