Simplify handling of /dev/null redirection in Eshell

This also fixes an issue where "echo hi > foo > /dev/null" didn't
write to the file "foo".  (Note that users can still use their
system's null device name when redirecting; Eshell doesn't need to do
anything special to support that.)

* lisp/eshell/esh-io.el (eshell-virtual-targets): Add "/dev/null".
(eshell-set-output-handle): Handle 'eshell-null-device'.

* test/lisp/eshell/esh-io-tests.el
(esh-io-test/redirect-subcommands/dev-null)
(esh-io-test/virtual/dev-null, esh-io-test/virtual/dev-null/multiple):
New tests.
This commit is contained in:
Jim Porter 2022-12-20 13:47:20 -08:00
parent 6defbd65b6
commit 17bf6a829c
2 changed files with 58 additions and 29 deletions

View file

@ -116,16 +116,22 @@ from executing while Emacs is redisplaying."
:group 'eshell-io)
(defcustom eshell-virtual-targets
'(("/dev/eshell" eshell-interactive-print nil)
'(;; The literal string "/dev/null" is intentional here. It just
;; provides compatibility so that users can redirect to
;; "/dev/null" no matter the actual value of `null-device'.
("/dev/null" (lambda (_mode) (throw 'eshell-null-device t)) t)
("/dev/eshell" eshell-interactive-print nil)
("/dev/kill" (lambda (mode)
(if (eq mode 'overwrite)
(kill-new ""))
'eshell-kill-append) t)
(when (eq mode 'overwrite)
(kill-new ""))
#'eshell-kill-append)
t)
("/dev/clip" (lambda (mode)
(if (eq mode 'overwrite)
(let ((select-enable-clipboard t))
(kill-new "")))
'eshell-clipboard-append) t))
(when (eq mode 'overwrite)
(let ((select-enable-clipboard t))
(kill-new "")))
#'eshell-clipboard-append)
t))
"Map virtual devices name to Emacs Lisp functions.
If the user specifies any of the filenames above as a redirection
target, the function in the second element will be called.
@ -138,10 +144,8 @@ function.
The output function is then called repeatedly with single strings,
which represents successive pieces of the output of the command, until nil
is passed, meaning EOF.
NOTE: /dev/null is handled specially as a virtual target, and should
not be added to this variable."
is passed, meaning EOF."
:version "30.1"
:type '(repeat
(list (string :tag "Target")
function
@ -357,21 +361,17 @@ the value already set in `eshell-last-command-result'."
"Set handle INDEX for the current HANDLES to point to TARGET using MODE.
If HANDLES is nil, use `eshell-current-handles'."
(when target
(let ((handles (or handles eshell-current-handles)))
(if (and (stringp target)
(string= target (null-device)))
(aset handles index nil)
(let* ((where (eshell-get-target target mode))
(handle (or (aref handles index)
(aset handles index (list nil nil 1))))
(current (car handle))
(defaultp (cadr handle)))
(if (not defaultp)
(unless (member where current)
(setq current (append current (list where))))
(setq current (list where)))
(setcar handle current)
(setcar (cdr handle) nil))))))
(let* ((handles (or handles eshell-current-handles))
(handle (or (aref handles index)
(aset handles index (list nil nil 1))))
(defaultp (cadr handle))
(current (unless defaultp (car handle))))
(catch 'eshell-null-device
(let ((where (eshell-get-target target mode)))
(unless (member where current)
(setq current (append current (list where))))))
(setcar handle current)
(setcar (cdr handle) nil))))
(defun eshell-copy-output-handle (index index-to-copy &optional handles)
"Copy the handle INDEX-TO-COPY to INDEX for the current HANDLES.

View file

@ -166,6 +166,17 @@ ensure only its statement is redirected."
(should (equal (buffer-string) "bar")))
(should (equal (buffer-string) "foobaz"))))
(ert-deftest esh-io-test/redirect-subcommands/dev-null ()
"Check that redirecting subcommands applies to all subcommands.
Include a redirect to /dev/null to ensure it only applies to its
statement."
(eshell-with-temp-buffer bufname "old"
(with-temp-eshell
(eshell-insert-command
(format "{echo foo; echo bar > /dev/null; echo baz} > #<%s>"
bufname)))
(should (equal (buffer-string) "foobaz"))))
(ert-deftest esh-io-test/redirect-subcommands/interpolated ()
"Check that redirecting interpolated subcommands applies to all subcommands."
(eshell-with-temp-buffer bufname "old"
@ -302,12 +313,30 @@ stdout originally pointed (the terminal)."
;; Virtual targets
(ert-deftest esh-io-test/virtual-dev-eshell ()
(ert-deftest esh-io-test/virtual/dev-null ()
"Check that redirecting to /dev/null works."
(with-temp-eshell
(eshell-match-command-output "echo hi > /dev/null" "\\`\\'")))
(ert-deftest esh-io-test/virtual/dev-null/multiple ()
"Check that redirecting to /dev/null works alongside other redirections."
(eshell-with-temp-buffer bufname "old"
(with-temp-eshell
(eshell-match-command-output
(format "echo new > /dev/null > #<%s>" bufname) "\\`\\'"))
(should (equal (buffer-string) "new")))
(eshell-with-temp-buffer bufname "old"
(with-temp-eshell
(eshell-match-command-output
(format "echo new > #<%s> > /dev/null" bufname) "\\`\\'"))
(should (equal (buffer-string) "new"))))
(ert-deftest esh-io-test/virtual/dev-eshell ()
"Check that redirecting to /dev/eshell works."
(with-temp-eshell
(eshell-match-command-output "echo hi > /dev/eshell" "hi")))
(ert-deftest esh-io-test/virtual-dev-kill ()
(ert-deftest esh-io-test/virtual/dev-kill ()
"Check that redirecting to /dev/kill works."
(with-temp-eshell
(eshell-insert-command "echo one > /dev/kill")