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.
This commit is contained in:
Jim Porter 2024-01-25 20:58:34 -08:00
parent f897b82ab1
commit 723b097351
4 changed files with 50 additions and 22 deletions

View 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

View file

@ -656,6 +656,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

View 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."

View 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"))))
;; Variable aliases