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
, anddirs
- 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 historyM-r
select from historyC-c C-p
move to previous promptsC-c C-l
list history in buffer
- Tempted to think
eshell
is likeshell
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:
- Eshell aliases
- Emacs functions that being with
eshell/
prefix - Normal Emacs functions
(don’t need to be
interactive
) - Shell executables
Of course, this is customizable:
eshell-prefer-lisp-functions
prefer Lisp functions to external commandseshell-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 directoriesr
if readablew
if writableL
filtering based on file sizem
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
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 ofgrep
replace-string
, et. al instead ofsed
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…