Adam Oudad

Adam Oudad

(Machine) Learning log.

3 minutes read

Being able to automate things with Emacs can be painfully hard at times, and ridiculously easy, once we get our hands on the right tool.

Let's illustrate this by solving the following problem with Emacs.

How to run a command on a remote server?

There are many commands, in Emacs, for running a subprocess or an inferior shell. For example

  • process-file
  • start-file-process
  • start-process
  • shell-command
  • shell

In a following article, I plan to cover all of these commands to show in what they differ.

But for now, we are interested in shell-command, or more specifically shell-command-to-string, because we may be interested in the output of the program, to be used downstream.

Run a command on a remote server

Let's assume we have a remote machine wisefully called remote, and BASH is the default shell on that machine.

In the following example, we run the command echo on the server remote.

(let ((default-directory (expand-file-name "/ssh:remote:~/")))
  (with-connection-local-variables
   (shell-command-to-string "echo Hi! I am on $HOSTNAME")
      ))
Hi! I am on remote

You can pass any bash script to shell-command-to-string and it will return the output as a string.

If you tried this, and only Hi! I am on . appears, then you likely need to set your shell to BASH. Let's see how to run the shell we want.

Change the shell used for the remote command

The variable shell-file-name holds the path to the shell used by commands like shell, shell-command, shell-command-to-string. So we have to change it.

But since we run the command on a remote server through SSH, our local value will not affect the value of shell-file-name used on the remote. Also, wrapping our previous code in a let statement to redefine the variable will not work, because we are only changing the globally scoped variable, not the one used by TRAMP to make the SSH connection.

Thanks to the recently introduced connection-local variables, we can set variables to be used by TRAMP to the values we want, on specific connections. It gives a great amount of flexibility. Here is how.

(connection-local-set-profile-variables
 'remote-fish
 '((shell-file-name . "/bin/fish")
   (shell-command-switch . "-c")
   (shell-interactive-switch . "-i")
   (shell-login-switch . "-l")))

(connection-local-set-profiles
 '(:application tramp :protocol "ssh" :machine "remote")
 'remote-fish)
(let ((default-directory (expand-file-name "/ssh:remote:~/")))
  (with-connection-local-variables
   (shell-command-to-string "echo Hi! I am on $hostname")
      ))
Hi! I am on remote

We were able to print the value of $hostname which would not be defined in ZSH or BASH, because we have set remote-fish to hold the local variables we needed.

The setting was in three steps.

  1. We create new profile called remote-fish, which provides an association list of variables and their respective value. This is done with the function connection-local-set-profile-variables.
  2. We set the newly created profile to match a certain connection by tramp. This is done by the function connection-local-set-profile. You can actually specify for which protocol and which machine this profile should be used.
  3. We finally wrap the command shell-command-to-string inside a with-connection-local-variables statement.

Let's step up again and see how to change environment variables.

Change environment variables

The environment variables that a process ran from Emacs will see are defined by the variable process-environment.

This time, there is no trick, and we can just set this variable in the let statement like so.

(let ((default-directory (expand-file-name "/ssh:remote:~/"))
      (process-environment '("MYVAR=foobar")))
  (with-connection-local-variables
   (shell-command-to-string "echo Hi! I am on $HOSTNAME. Also MYVAR is set to $MYVAR.")
   ))
Hi! I am on remote. Also MYVAR is set to foobar.

We just use our profile set for remote by wrapping the shell command call inside with-connection-local-variables.

That's all!

Thank you for reading :) Adam.

comments powered by Disqus

Recent posts

See more

Categories

About

This website is a weblog were I write about computer science, machine learning, language learning.