Presenting 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

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

What’s all this then?

  • What eshell really is
  • How to use
  • Hacking

    FYI: Deep content coming up… Watch this as a recording later (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 (aka. shell history)

Shell… The Bad

  • Commands? Like key sequences, only longer
  • Needing completion to run commands?
  • Loops? Not terrible

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!

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
    • Shell-focused program worker
  • Eshell marries both pretty well:
    • Concept of a current directory
    • popd, pushd, and dirs
    • Globbing Expressions
    • Aliases: alias ll 'ls -l'
  • Tempted to think eshell is like shell

EShell as a REPL

  • Lisp expressions just work with parens
  • Parens kinda optional (more later)
  • Eshell distinguishes numbers from strings
  • Quotes often optional
    • Do you care about spaces?
    • Double and single quotes are interchangeable

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

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)))

Eshell’s Parser Expressions

  • eshellcan you say braces?
    no parens
    assumes strings and globs $vars
    { ... }
    grouping expressions
    ${ ... }
    evaluation for strings
  • elispall parens all the time
    ( ... )
    is fully Lisp-centric
    $( ... )
    evaluation for strings and string substitution

Note: You can mix your expression parsing

Shell-like Loops

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

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

Modifiers

Got a list of files?

Syntactic sugar to convert strings and lists.

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!

Extending Predicates

The User predicate (U) could have been written:

(defun file-owned-current-uid-p (file)
  (if (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))))

I have a directory stuffed with files.

Most of my files have #+tags entries.

I can filter based on these tag entries.

I have to parse text following predicate key.

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

Replacing Pipes

Pipes are Flexible, but…

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

Instead, redirect output to Emacs buffer:

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

Reference buffers as #<buf-name> with:

(setq eshell-buffer-shorthand t)

Emacs is pretty good at text processing

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

Been thinking of something like:

$ openstack server list > pipe-edit saved-results.txt

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")
      

Questions?