1

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
1
2
Done
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. – DoMiNeLa10 Mar 6 at 22:42
1

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))
    (with-silent-modifications
      ;; FIXME: Why not use a temporary buffer and avoid this
      ;; "insert&delete" business?  --Stef
      (insert string)
      (goto-char (point-max))
      (throw 'eshell-replace-command
             (prog1
                 (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)))
                         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)
        ,doc
        (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
1
2
DONE
/Path/to/current/dir $
  • Thanks for this snippet! But it is not possible to C-c the running process. – lewo Mar 8 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 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 at 16:47
  • Sorry about above formatting... I'm no longer able to edit it :/ – lewo Mar 10 at 16:54
  • Actually, I didn't have reloaded Eshell:/ – lewo Mar 10 at 19:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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