From f866db2e83d85ba6f7bcfca4f0910c7671b45b7d Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Thu, 25 Jan 2024 20:58:34 -0800 Subject: [PATCH] Add support for running commands via Eshell's "env" command * (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 | 8 +++-- etc/NEWS | 7 +++++ lisp/eshell/esh-var.el | 50 +++++++++++++++++++------------ test/lisp/eshell/esh-var-tests.el | 7 +++++ 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index fb9a563b696..da5e1ef1d03 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -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 diff --git a/etc/NEWS b/etc/NEWS index 0a0c333f105..f866595e68f 100644 --- 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 '#'. This special reference type returns a marker at 'POSITION' in diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el index 1d90fbdd8ee..627cbb17797 100644 --- a/lisp/eshell/esh-var.el +++ b/lisp/eshell/esh-var.el @@ -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 '." +(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 '." + (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 "") - (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." diff --git a/test/lisp/eshell/esh-var-tests.el b/test/lisp/eshell/esh-var-tests.el index bb3d18abf6d..b94e8a276d7 100644 --- a/test/lisp/eshell/esh-var-tests.el +++ b/test/lisp/eshell/esh-var-tests.el @@ -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")))) + ;; Variable aliases -- 2.39.5