💾 Archived View for gem.librehacker.com › gemlog › starlog › 20240509-0.gmi captured on 2024-06-16 at 12:10:36. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2024-05-10)

-=-=-=-=-=-=-

Emacs as a Shell: Part 2 (publ. 2024-05-09)

Just to clarify, I think that shell-command and async-shell-command are convenient functions. And since you can call them as functions in normal elisp code, that gives you a lot of freedom for constructing commands and arguments and such. They just don't take me quite as far as I want to go in making Emacs itself the shell, i.e., the interface to the operating system, since everything gets passed to bash, or whatever other shell, in the end.

Naturally enough, the discussion turns now to eshell, which is a great tool if you approach it with the right goals and expectations. Again, if what you are looking for is a one-to-one bash clone that displays all your fancy ANSI terminal output beautifully, then we are headed in the wrong direction. But what eshell does give us is

(1) A way to enter commands and arguments separated by spaces, without parentheses

(2) A way to use pipes and output redirection (input redirection is not yet implemented)

(3) A way to conveniently integrate elisp into the command line call when desired, including any emacs function we might normally use

(4) A means to use a buffer as a source of input or sink for output

(5) eshell-command, similar to shell-command and async-shell-command, to run a command outside of an eshell interpreter buffer

(6) A way to integregate eshell command line calls into regular elisp

and various other features. See the eshell info manual.

As an example of (3), we can run

find-file README.md

to open README.md, if that is more convenient than M-x find-file.

Regarding (4), using a buffer for an output sink is straightforward:

echo "bla bla bla" > #<*test-buffer*>

Using a buffer for an input source is a little more complicated, since we have to convert the buffer to a string first. We could do something like so:

(with-current-buffer "*test-buffer*" (buffer-string)) | grep bla
(standard input):bla bla bla

But it is convenient to wrap that up in a short function:

(defun @ (buffer-or-name)
    (with-current-buffer buffer-or-name (buffer-string)))

Now we can do

@ "*test-buffer*" | grep bla

Regarding (5), eshell-command works similar to shell-command, and you add an "&" at the end to make it asynchronous. Unfortunately eshell-command is not bound to any convenient keybinding by default. I'm using M-C-! at present.

Regarding (6), eshell-command is a normal elisp function that can be used non-interactively. So you can insert something like (eshell-command "ls -1 *2023*.gmi > #<*2023-entries*>") into an elisp function if you so desired.

Of course, a lot more could be said about the nuances and caveats of this. I'll just give one caveat: You'll want to read-up on the section about switching between Emacs pipes (|) vs. OS pipes (*|) and efficiency concerns that are at play.

I've found that in practice, it is very helpful to have a function like so in your init.el:

(defun eshell-with-directory (path)
  "Prompt for a directory. Then, launch a new eshell buffer and change \
directory to the specified directory."
  (interactive "Ddirectory: ")
  (let ((default-directory path))
    (eshell "new")))

It is important to emphasize that eshell is just another tool in the Emacs kit for interfacing with your OS. There is no reason you have to run an eshell command if it is more convenient to use, e.g., dired functionality or M-x find-file. The goal is to make Emacs your convenient, powerful interface to your operating system.

There are a few more thoughts floating around in the back of my mind on related subjects, such as make-process, and feeding data into Emacs. But I'm not sure if they are developed enough yet to be worthy of another post.