Presenting the EShell

Outline

  • Introduction:
  • Typical Shell Features
  • Typical Lisp REPL features:
  • Advanced Features
  • Modifications/Extensions:

Note: We must make this into a workshop … type this, then this.

Introduction

Hi, I’m Howard Abrams … I lead the PDX Emacs Hackers Let’s chat about EShell

John Wiegley created EShell in 1998:

…as a way to provide a UNIX-like environment on a Windows NT machine.

Part of Emacs since v21.

Personally?

  • Started with ksh
  • Used a lot of shells…
  • Tried eshell soon after its birth
  • Shelved it since it wasn’t shell-enough
  • Rediscovered years later
  • Finally got it

Contents: What’s all this then?

  • What EShell really is
  • How to use it effectively
  • Hacking

FYI: Deep content coming up… Watch the recording later, as I’ll upload this org-mode file.

Shell… The Good

  • Can be immensely powerful… at times
  • Pipes and redirection are a staple
  • Utilizing small, focused text-oriented executables
  • Complex command re-invocation
  • History okay … nothing like Emacs

Shell… The Bad

  • Commands? Like key sequences, only longer
  • Needing completion to run commands?
  • Loops? Not terrible
  • Copy and pasting … with a mouse!? (at least use M-x shell)

Shell… The WTF?

  • Best part: extensibility!
  • But what an awful language:

    if [ $(echo "$IN" | cut -c 1-3 ) == 'abc' ]; then
       # ...
    fi
    
  • May be Turing complete, but so what.

    But, but, but… we know the shell!

Seen Rich Hickey’s Simple Made Easy talk? The shell is easy because it is so close.

iPython

Python REPL with shell-like features.

  • Understands a current directory
  • Has some shell-like commands, cat
  • Doesn’t easily execute programs: system
  • Executes Python scripts: run

EShell as a Shell

  • Most “interactive language” interfaces choose:
    • Language-specific REPL, or
    • Shell-focused program worker
  • As a shell:
    • Concept of a current directory
    • popd, pushd, and dirs
    • Globbing Expressions
    • Quotes often optional
      • Do you care about spaces?
      • Double and single quotes are interchangeable
    • Aliases: alias ll 'ls -l'
  • Emacs shell interaction:
    • M-n / M-p scroll through history
    • M-r select from history
    • C-c C-p move to previous prompts
    • C-c C-l list history in buffer
  • Tempted to think eshell is like shell

EShell as a REPL

  • Lisp expressions work within parens
  • Unlike shell, EShell:
    • Commands can be executables or Emacs functions
    • Distinguishes strings, numbers, and lists
  • EShell is marriage of two syntax parsers:
    • Shell Expressions
    • Lisp Expressions
  • A single line can mix the two!

Eshell’s Parsers

  • Lisp parser:
    • ( ... )
    • $( ... ) … useful for string evaluation
  • Shell parser:
    • no parens … in other words, the default
    • { ... }
    • ${ ... } … useful for string evaluation
  • In shell parser, reference variables with $

Shell-like Loops

  • Syntactic sugar around loop.
  • Code following in is a generate list
  • Use trailing { ... } for side-effects

Function or Executable?

What about the executable find vs. Emacs’ find function?

Precedence Order:

  1. Eshell aliases
  2. Emacs functions that being with eshell/ prefix
  3. Normal Emacs functions (don’t need to be interactive)
  4. Shell executables

Of course, this is customizable:

  • eshell-prefer-lisp-functions prefer Lisp functions to external commands
  • eshell-prefer-lisp-variables prefer Lisp variables to environmentals

Globbin’ Filters

  • The * glob-thing has filters
  • Great if you can remember the syntax:
    • . for files
    • / for directories
    • r if readable
    • w if writable
    • L filtering based on file size
    • m filtering on modification time
  • The filters can be stacked, e.g. .L
  • Can’t remember? C-c M-q Or: eshell-display-predicate-help

Modifiers

Syntactic sugar to convert strings and lists.

Can’t remember? C-c M-m Or: eshell-display-modifier-help

Eshell filters and modifiers remind me of regular expressions

Don’t know the eshell-way? Just drizzle Lisp.

EShell Hack Points

While offering similar shell experience, Eshell is really hackable!

Write your Own Functions

  • Functions for Eshell: eshell/
  • They do not need to be interactive
  • Functions should assume &rest for arguments:

    (defun eshell/do-work (&rest args)
      "Do some work in an optional directory."
      (let ((some-dir (if args
                           (pop args)
                        default-directory)))
        (message "Work in %s" some-dir)))
    

Remote Connections

To have eshell work on a remote server:

(let ((default-directory "/ssh:your-host.com:public/"))
  (eshell))

My personal project:

  • Connect to my hypervisor controller
  • Download and store a list of virtual machines
  • Use ido-completing-read to select a host / ip
  • Generate a Tramp URL for default-directory
eshell-present-fav-hosts.png

Extending Predicates

The User predicate (U) could have been written:

(defun file-owned-current-uid-p (file)
  (when (file-exists-p file)
      (= (nth 2 (file-attributes file))
         (user-uid))))

Then add it:

(add-hook 'eshell-pred-load-hook (lambda ()
  (add-to-list 'eshell-predicate-alist
           '(?U . 'file-owned-current-uid-p))))

My engineering notebook is a directory of files.

Most of my files have #+tags entries.

I can filter based on these tag entries.

I have to parse text following predicate key.

Replacing Pipes

Pipes for shell are flexible, but…

  • Shell’s text processing is limited
  • Need arsenal of tiny, cryptic programs
  • Re-run many times since debug pipe steps

Emacs is pretty good at text processing

  • keep-lines / flush-lines instead of grep
  • replace-string, et. al instead of sed

In EShell, redirect output to Emacs buffer:

$ some-command > #<buffer buf-name>

After editing the buffer, use it:

$ bargs #<buf-name> mv % /tmp/testing

Reference buffers as #<buf-name> with:

(setq eshell-buffer-shorthand t)

Or use keybinding, C-c M-b

Bargs Code

Initial implementation of bargs:

(defun eshell/-buffer-as-args (buffer separator command)
  "Takes the contents of BUFFER, and splits it on SEPARATOR, and
runs the COMMAND with the contents as arguments. Use an argument
`%' to substitute the contents at a particular point, otherwise,
they are appended."
  (let* ((lines (with-current-buffer buffer
                  (split-string
                   (buffer-substring-no-properties (point-min) (point-max))
                   separator)))
         (subcmd (if (-contains? command "%")
                     (-flatten (-replace "%" lines command))
                   (-concat command lines)))
         (cmd-str  (string-join subcmd " ")))
    (message cmd-str)
    (eshell-command-result cmd-str)))

(defun eshell/bargs (buffer &rest command)
  "Passes the lines from BUFFER as arguments to COMMAND."
  (eshell/-buffer-as-args buffer "\n" command))

(defun eshell/sargs (buffer &rest command)
  "Passes the words from BUFFER as arguments to COMMAND."
  (eshell/-buffer-as-args buffer nil command))

EShell Summary

  • Advantages:
    • Similar shell experience between operating systems
    • Much more extendable, hackable and funner
  • Disadvantages:
    • Pipes go through Emacs buffers… not efficient
    • Programs that need special displays:

      (add-to-list 'eshell-visual-commands "top")
      

      For commands that have options that trigger curses/pager:

      (add-to-list 'eshell-visual-options '("git" "--help"))
      

      If command has a ncurses/pager sub-commands, use:

      (add-to-list 'eshell-visual-subcommands
         '("git" "log" "diff" "show"))
      

      Also set eshell-destroy-buffer-when-process-dies.

      My goal was to inspire potential hackery…

Questions?