]> git.eshelyaron.com Git - emacs.git/commitdiff
Add support for running commands via Eshell's "env" command
authorJim Porter <jporterbugs@gmail.com>
Fri, 26 Jan 2024 04:58:34 +0000 (20:58 -0800)
committerEshel Yaron <me@eshelyaron.com>
Fri, 26 Jan 2024 11:24:14 +0000 (12:24 +0100)
* (eshell-handle-local-variables): Move most of the code to...
(eshell-parse-local-variables): ... here.
(eshell/env): Call 'eshell-parse-local-variables'.

* test/lisp/eshell/esh-var-tests.el
(esh-var-test/local-variables/env): New test.

* doc/misc/eshell.texi (Built-ins): Describe the new behavior.

* etc/NEWS: Announce this change.

(cherry picked from commit 723b0973512c0e6e9fb0f07678124347ccd44b54)

doc/misc/eshell.texi
etc/NEWS
lisp/eshell/esh-var.el
test/lisp/eshell/esh-var-tests.el

index fb9a563b696f5089c4f932dc4cb8b900a27e952c..da5e1ef1d0303c53c67ed2cd46c99a752377a9a2 100644 (file)
@@ -624,9 +624,11 @@ each argument as a string, separated by a space.
 
 @item env
 @cmindex env
-Prints the current environment variables.  Unlike in Bash, this
-command does not yet support running commands with a modified
-environment.
+With no arguments, print the current environment variables.  If you
+pass arguments to this command, then @command{env} will execute the
+arguments as a command.  If you pass any initial arguments of the form
+@samp{@var{var}=@var{value}}, @command{env} will first set @var{var}
+to @var{value} before running the command.
 
 @item eshell-debug
 @cmindex eshell-debug
index 0a0c333f105593684f6ccbe5beb1218978ebb17c..f866595e68f8e1e5db8ac694e86af49a60791202 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -633,6 +633,13 @@ appropriate, but still allow piping the output elsewhere if desired.
 For more information, see the "(eshell) Built-ins" node in the Eshell
 manual.
 
++++
+*** Eshell's 'env' command now supports running commands.
+Like in many other shells, Eshell's 'env' command now lets you run a
+command passed as arguments to 'env'.  If you pass any initial
+arguments of the form 'VAR=VALUE', 'env' will first set 'VAR' to
+'VALUE' before running the command.
+
 +++
 *** New special reference type '#<marker POSITION BUFFER>'.
 This special reference type returns a marker at 'POSITION' in
index 1d90fbdd8ee2ec9435c47b0eeaca4cd4150e2e20..627cbb17797543eac9bf836863a188c0e4e6f29a 100644 (file)
@@ -304,27 +304,36 @@ This is set to t in `eshell-local-variable-bindings' (which see).")
     (add-hook 'pcomplete-try-first-hook
              #'eshell-complete-variable-assignment nil t)))
 
-(defun eshell-handle-local-variables ()
-  "Allow for the syntax `VAR=val <command> <args>'."
+(defun eshell-parse-local-variables (args)
+  "Parse a list of ARGS, looking for variable assignments.
+Variable assignments are of the form \"VAR=value\".  If ARGS
+begins with any such assignments, throw `eshell-replace-command'
+with a form that will temporarily set those variables.
+Otherwise, return nil."
   ;; Handle local variable settings by let-binding the entries in
   ;; `eshell-local-variable-bindings' and calling `eshell-set-variable'
   ;; for each variable before the command is invoked.
   (let ((setvar "\\`\\([A-Za-z_][A-Za-z0-9_]*\\)=\\(.*\\)\\'")
-        (command eshell-last-command-name)
-        (args eshell-last-arguments))
-    (when (and (stringp command) (string-match setvar command))
+        (head (car args))
+        (rest (cdr args)))
+    (when (and (stringp head) (string-match setvar head))
       (throw 'eshell-replace-command
              `(let ,eshell-local-variable-bindings
                 ,@(let (locals)
-                    (while (and (stringp command)
-                                (string-match setvar command))
+                    (while (and (stringp head)
+                                (string-match setvar head))
                       (push `(eshell-set-variable
-                              ,(match-string 1 command)
-                              ,(match-string 2 command))
+                              ,(match-string 1 head)
+                              ,(match-string 2 head))
                             locals)
-                      (setq command (pop args)))
+                      (setq head (pop rest)))
                     (nreverse locals))
-                 (eshell-named-command ,command ,(list 'quote args)))))))
+                 (eshell-named-command ,head ',rest))))))
+
+(defun eshell-handle-local-variables ()
+  "Allow for the syntax `VAR=val <command> <args>'."
+  (eshell-parse-local-variables (cons eshell-last-command-name
+                                      eshell-last-arguments)))
 
 (defun eshell-interpolate-variable ()
   "Parse a variable interpolation.
@@ -414,19 +423,22 @@ the values of nil for each."
                                               obarray #'boundp))
              (pcomplete-here))))
 
-;; FIXME the real "env" command does more than this, it runs a program
-;; in a modified environment.
 (defun eshell/env (&rest args)
   "Implementation of `env' in Lisp."
-  (eshell-init-print-buffer)
   (eshell-eval-using-options
    "env" args
-   '((?h "help" nil nil "show this usage screen")
+   '(;; FIXME: Support more "env" options, like "--unset".
+     (?h "help" nil nil "show this usage screen")
      :external "env"
-     :usage "<no arguments>")
-   (dolist (setting (sort (eshell-environment-variables) 'string-lessp))
-     (eshell-buffered-print setting "\n"))
-   (eshell-flush)))
+     :parse-leading-options-only
+     :usage "[NAME=VALUE]... [COMMAND [ARG]...]")
+   (if args
+       (or (eshell-parse-local-variables args)
+           (eshell-named-command (car args) (cdr args)))
+     (eshell-init-print-buffer)
+     (dolist (setting (sort (eshell-environment-variables) 'string-lessp))
+       (eshell-buffered-print setting "\n"))
+     (eshell-flush))))
 
 (defun eshell-insert-envvar (envvar-name)
   "Insert ENVVAR-NAME into the current buffer at point."
index bb3d18abf6d0c41060693c81572ca742868ce7b2..b94e8a276d7fe3d57738fc07290821b8e4c7a4ac 100644 (file)
@@ -661,6 +661,13 @@ nil, use FUNCTION instead."
      (eshell-insert-command "VAR=hello cd ..")
      (should (equal default-directory parent-directory)))))
 
+(ert-deftest esh-var-test/local-variables/env ()
+  "Test that \"env VAR=value command\" temporarily sets variables."
+  (with-temp-eshell
+   (push "VAR=value" process-environment)
+   (eshell-match-command-output "env VAR=hello env" "VAR=hello\n")
+   (should (equal (getenv "VAR") "value"))))
+
 \f
 ;; Variable aliases