This post is part of a series about shells in Emacs:
- Painless Emacs shell commands
- Painless Emacs interactive shells
- Emacs shell interpreter configuration (current)
- Painless Emacs remote shells
In this series of posts we discuss spawning shells from within Emacs.
If the functions we previously introduced can be used programmatically, their main use-case is to be called directly by the user with
M-x, i.e. as commands.
In this case, the user is not prompted for the shell interpreter to use.
This post explores the different ways to configure the interpreter to be used.
Custom wrapper commands
One strategy is to define commands for specific connection / interpreter combinations:
;; native (defun my-zsh-on-rapsi () (interactive) (let ((default-directory "/ssh:pi@raspberry:~") (current-prefix-arg '(4)) ; don't prompt for interpreter (explicit-shell-file-name "zsh")) (shell))) ;; using `friendly-shell' (defun my-zsh-on-raspi () (interactive) (friendly-shell :path "/ssh:pi@raspberry:/~" :interpreter "zsh"))
This is convenient if we can want to be able to spawn several shells with different interpreters for a same connection string (
Default local interpreter declaration
The default interpreter can be customized like so:
(setq explicit-shell-file-name "/bin/zsh" shell-file-name "/bin/zsh" explicit-zsh-args "-i" shell-command-switch "-c")
Static remote connection interpreter declaration (native)
Emacs 26.2 comes with a new feature that can help us customize remote connection interpreters.
This feature is connection-local vars, i.e. custom var values for a given context (in this case TRAMP connections). It’s akin to buffer-local vars.
Let’s say that we always want to connect to a server with the
(connection-local-set-profile-variables 'zsh ; this is an alias, you can name it as you like '((explicit-shell-file-name . "/bin/zsh") ;; you can set any var here but it only get useful w/ TRAMP-related vars, e.g.: (explicit-bash-args . ("-i")) ;; ... )) (connection-local-set-profiles '( :application tramp :protocol "ssh" :user "pi" :machine "raspberry") 'zsh) ; use same alias as before
We can even choose to use
zsh for all remote ssh connections,
nil acting as a wild card:
(connection-local-set-profiles '( :application tramp :protocol "ssh" :user nil ; any user :machine nil) ; any host 'zsh)
Better static remote connection interpreter declaration
Connection-local vars are a great idea but the implementation is far from ideal.
Indeed, the 2-step declaration is cumbersome and a declaration cannot be undone unless we restart the Emacs process1.
More importantly, instead of using
nil as a wildcard, proper regexps would have been a better design decision.
In an enterprise setting, when dealing with thousands of VMs / containers, you may want to have configurations based on host naming conventions or IP ranges.
That’s why the friendly-shell* packages2 now come with its custom implementation of connection-local vars.
The previous example becomes:
(add-to-list with-shell-interpreter-connection-local-vars '(".*@rasp.*") . ((explicit-shell-file-name . "/bin/zsh") (explicit-bash-args . ("-i")) (shell-command-switch . "-c")))
This way every connection with any user to any host starting with the string
"rasp" would use the zsh interpreter.
This configuration only gets applied to the
friendly-shell* family of commands.
The syntax of the configuration is heavily inspired by this original idea from @riscy.
If you want to use those commands with the native connection-local vars implementation instead, put this in your init:
(setq with-shell-interpreter-connection-local-vars-implem 'native)