Helper for Emacs shell command APIs, making implicit argument as explicit keyword arguments.
This package is a library and does not provide any command.
It is inspired by the eval-after-load
/ with-eval-after-load
functions.
It does the heavy lifting behind packages friendly-shell-command and friendly-shell.
For more context, read the accompanying blog post.
The package is available on Melpa.
With use-package
:
(use-package with-shell-interpreter)
Manually:
M-x package-install with-shell-interpreter
We recommend using the macro with-shell-interpreter
. It's a more convenient version of with-shell-interpreter-eval
that prevents having to quote :form and wrap it in a progn
.
keyword argument | implicit var being let-bound | mandatory? | description |
---|---|---|---|
:form | n/a | ✔️ | The s-expressions to eval. |
:path | default-directory |
❌ | The path from which to eval. |
:interpreter | explicit-shell-file-name / shell-file-name |
❌ | Name or absolute path of shell interpreter executable. |
:interpreter-args | explicit-INTEPRETER-args |
❌ | Login args to call interpreter with for login. |
:command-switch | shell-command-switch |
❌ | Command switch arg for asking interpreter to run a shell command. |
:w32-arg-quote | w32-quote-process-args |
❌ | Character to use for quoting shell arguments (only on the Windows build of Emacs) |
:allow-local-vars | n/a | ❌ | Whether to allow buffer-local and/or connection-local values |
:form is expected to contain calls to functions relying on the Emacs shell APIs (e.g. shell
, shell-command
, async-shell-command
and shell-command-to-string
).
Setting :path to a remote location (with TRAMP format, i.e. /<method>:<user>@<host>:<localname>
) allows running form with interpreter of remote server.
:interpreter-args is only usefull for interactive shells (from package shell-mode).
:command-switch is only usefull for single shell commands (from package simple).
:allow-local-vars can take the following values:
- 'buffer: allow buffer-local vars values_
- 'connection: allow connection-local values
- 'both: allow both types of local values
- 'none: ignore all local values
If left empty, here are the default values being used:
keyword argument | fallback value (local path) | fallback value (remote path) |
---|---|---|
:path | current default-directory |
current default-directory |
:interpreter | shell-file-name |
with-shell-interpreter-default-remote |
:interpreter-args | explicit-INTEPRETER-args if set |
with-shell-interpreter-default-remote-args |
:command-switch | shell-command-switch |
with-shell-interpreter-default-remote-command-switch |
:w32-arg-quote | w32-quote-process-args |
w32-quote-process-args |
:allow-local-vars | nil | 'connection |
Getting the temperature from a Raspberry Pi:
(with-shell-interpreter
:path "/ssh:pi@raspberry:/~"
:interpreter "bash"
:form
(shell-command-to-string "vcgencmd measure_temp"))
Under Microsoft Windows, launching an interactive shell with the git-bash interpreter:
(with-shell-interpreter
:path "~" ; ensure local path
:interpreter "C:/Program Files/Git/bin/bash.exe"
:form
(let (current-prefix-arg '(4)) ; don't prompt user for interpreter
(shell)))
For more practical examples, have a look at packages friendly-shell-command and friendly-shell (examples).
We generally want to have different values for remote connection as the user might have redefined the value of shell-file-name
with something exotic (e.g. zsh) and we would want a safer default for remote servers.
Furthermore, under Microsoft Windows, shell-file-name
defaults to cmdproxy.exe which is OK for local shells but sucks for remote ones...
We can of course use the :interpreter, :interpreter-args and :command-switch keyword parameters but it's cumbersome to define a function for each remote connection with a custom configuration.
Instead, the preferred way is to rely on connection-local variables.
with-shell-interpreter
comes by default with its own custom implementation of connection-local variables. It can be configured with var with-shell-interpreter-connection-local-vars
.
It comes with a fallback entry for default configuration of remote interpreters:
'((".*" . ((explicit-shell-file-name . "/bin/bash")
(explicit-bash-args . ("-c" "export EMACS=; export TERM=dumb; stty echo; bash"))
(shell-command-switch . "-c"))))
To add an entry, just:
(add-to-list with-shell-interpreter-connection-local-vars '(".*@openwrt") . ((explicit-shell-file-name . "/bin/ash")
(explicit-bash-args . ("-i"))
(shell-command-switch . "-c")))
Alternatively, you can instead choose to use Emacs' native implementation (more restrictive and cumbersome to configure):
(setq with-shell-interpreter-connection-local-vars-implem 'native)
The native implementation is only available since Emacs 26.1. Read docstrings of connection-local-set-profiles
and connection-local-set-profile-variables
for more details about it.
You might want to change the value of tramp-default-user
if you usually connect to remote host with a user different than your local one.
This code uses form feeds (^L
character) as separators.
Either package form-feed or page-break-lines makes them appear as intended.
Package lisp-extra-font-lock is also recommended to distinguish between local and global vars in let expressions.