I would like to create an elisp function such as

(defun script()
 (run "bash" "-c" "echo 1; sleep 3; echo 2")
 (eshell-print "Done")) 

From an Eshell, its execution should look like:

eshell $ script
eshell $

With the following requirements:

  • the stdout of the bash process has to be "streamed" in the eshell buffer.

  • it must be possible to C-c the script

Any idea on how to do that?

  • I assume you want the script to execute synchronously as well, so you can expect it's done when the function returns.
    – user12563
    Mar 6, 2019 at 22:42

2 Answers 2


You can copy the following Elisp code to your init files.

It contains a modified version of function eshell-source-file. Essentially I changed (insert-file-contents file) to (insert string). Furthermore I needed a minor modification of 'eshell-command-name.

It also contains the function define-eshell-script defining an Eshell command with a doc string and an Eshell script given by a string. The script takes care of the formalities for the script required by Eshell internals (e.g., correctly registering in the list variable eshell-complex-commands). Those formalities make the newly defined command interruptible.

(require 'cl-lib) ;; for macro `cl-pushnew' (not autoloaded ?!)
(require 'esh-cmd) ;; for variable `eshell-complex-commands'

(defun eshell-source-string (string &optional args subcommand-p)
  "Execute a series of Eshell commands in STRING, passing ARGS.
Comments begin with `#'."
  (interactive "sString to be evaluated as eshell script: ")
  (let ((orig (point))
    (here (point-max))
    (inhibit-point-motion-hooks t))
    (goto-char (point-max))
      ;; FIXME: Why not use a temporary buffer and avoid this
      ;; "insert&delete" business?  --Stef
      (insert string)
      (goto-char (point-max))
      (throw 'eshell-replace-command
                 (list 'let
                       (list (list 'eshell-command-name (list 'quote "source-string"))
                             (list 'eshell-command-arguments
                                   (list 'quote args)))
                       (let ((cmd (eshell-parse-command (cons here (point)))))
                         (if subcommand-p
                             (setq cmd (list 'eshell-as-subcommand cmd)))
               (delete-region here (point))
               (goto-char orig))))))

(defun define-eshell-script (cmd doc string)
  "Define CMD as eshell script STRING with documentation DOC.
CMD can be a string or a symbol."
  (let ((fun (intern (concat "eshell/" cmd))))
    (fset fun
      `(lambda (&rest args)
        (eshell-source-string ,string args)))
    (cl-pushnew cmd (default-value 'eshell-complex-commands) :test #'equal)
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
    (when (derived-mode-p 'eshell-mode)
      (cl-pushnew cmd eshell-complex-commands :test #'equal))))
    (put fun 'eshell-no-numeric-conversions t)))

If the above functions are available you can define your actual script with the help of define-eshell-script as follows.

(define-eshell-script "script" "The doc-string: Output 1, sleep, and output 2."
   "bash -c \"echo 1; sleep 3; echo 2;\"
echo DONE")

The usage example in Eshell looks like you want it:

/Path/to/current/dir $ script
/Path/to/current/dir $
  • Thanks for this snippet! But it is not possible to C-c the running process.
    – lewo
    Mar 8, 2019 at 12:20
  • @lewo The script must be registered in eshell-complex-commands to be interruptible. The new function define-eshell-script does that for you. In that case the eshell-replace-command is cought by Eshell and one does not need eshell-script-string anymore. I've deleted it.
    – Tobias
    Mar 9, 2019 at 1:33
  • Hmm, it fails in my emacs :/ $ script No catch for tag: eshell-replace-command, (let ((eshell-command-name (quote "")) (eshell-command-arguments (quote nil))) (progn (eshell-commands (eshell-trap-errors (eshell-named-command "bash" (list "-c" (eshell-escape-arg "echo 1; sleep 3; echo 2;"))))) (eshell-trap-errors (eshell-named-command "echo" (list "DONE")))))
    – lewo
    Mar 10, 2019 at 16:47
  • Sorry about above formatting... I'm no longer able to edit it :/
    – lewo
    Mar 10, 2019 at 16:54
  • Actually, I didn't have reloaded Eshell:/
    – lewo
    Mar 10, 2019 at 19:01

I am having a blast using eshell on MS Windows, even forgetting sometimes I am not on GNU/Linux, and I think I have two nice examples that some of you can relate to.

For console applications (your case):

(defun eshell/vbox-startvm-headless (name)
  "VBoxManage.exe startvm <vmname> --type headless"
  (let* ((cmdlist (list (shell-quote-argument (vboxmgr-path)) "startvm" name "--type" "headless")))
    (message (format "%S" cmdlist))
    (throw 'eshell-replace-command (eshell-parse-command (car cmdlist) (cdr cmdlist)))))

(add-to-list 'eshell-complex-commands "vbox-startvm-headless")

For GUI applications:

(defun eshell/virtualbox (&optional args)
  "VirtualBox.exe [args...]"
  (start-process "virtualbox" nil (virtualbox-path)))

The snippets above are from my init.el and they rely on vboxmgr-path and virtualbox-path to point to VBoxManage.exe and VirtualBox.exe, respectively.

Your Answer

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

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