4

I would like to be able to manage external processes from Emacs, in a way that is:

  • (a) safe w.r.t. arguments escaping for the shell,
  • (b) efficient both for local and remote processes (using TRAMP w/ ssh).

Here are two solutions I can think of to implement calling a possibly remote process:

  1. using shell-command: this has the advantage that it is fast (meets criterion b), but one needs to pass a full shell command (fails for criterion a).

  2. using start-file-process as recommended by the TRAMP documentation: this is nice in terms of arguments handling (criterion a), but a new SSH connection seems to be called for each process, which dramatically slows things down in remote configurations (criterion b).

NB: these solutions also differ in that (1) is synchronous whereas (2) is asynchronous, but I don't mention it because (a)synchronicity doesn't matter for my application.

It looks like option (2) is slow because it spawns a new ssh process, whereas option (1) reuses an already existing one. Is there any high-level function allowing to call a possibly remote process, while still being efficient by reusing an existing connection if possible?


Below is the code I used to benchmark both solutions:

(let ((test/remote-buffer (find-file "/xxx.xxx.xxx.xxx:/home/LOGIN/"))
      (test/output-buffer (get-buffer-create "*hostname*")))
  (with-current-buffer test/output-buffer
    (erase-buffer))

  (with-current-buffer test/remote-buffer
    (setq test/begin (current-time))
    (shell-command "hostname" test/output-buffer)
    (message "1-Elapsed: %f" (float-time (time-subtract (current-time) test/begin))))

  (with-current-buffer test/remote-buffer
    (setq test/begin (current-time))
    (set-process-sentinel
     (start-file-process "hostname" test/output-buffer "hostname")
     (lambda (p e)
       (message "2-Elapsed: %f" (float-time (time-subtract (current-time) test/begin)))))))

yielding output:

1-Elapsed: 0.010178
Tramp: Opening connection for xxx.xxx.xxx.xxx using scp...
Tramp: Sending command `exec ssh -q   -o ControlPath=/tmp/tramp.12155IuC.%r@%h:%p -o ControlMaster=auto -e none xxx.xxx.xxx.xxx'
Tramp: Waiting for prompts from remote shell...done
Tramp: Found remote shell prompt on `xxx.xxx.xxx.xxx'
Tramp: Opening connection for xxx.xxx.xxx.xxx using scp...done
2-Elapsed: 1.116449

In the case of local processes, both implementations take a similar elapsed time, which is negligible (and often faster for solution 2).

5

What about writing a "safe" version of shell-command that would properly escape arguments using shell-quote-argument ?

For example:

(defun safe-shell-command (args &optional output-buffer error-buffer)
  (apply 'shell-command (mapconcat 'shell-quote-argument args " ")
         output-buffer error-buffer nil))

(the same would easily apply to async-shell-command)

Obviously it has the same performance characteristics as shell-command

The manual has a section on shell arguments. It also mentions combine-and-quote-strings, which might be useful depending on the case: it produces "less quoted" arguments, for example $ characters are left alone.

  • Thanks, this does the job nicely and simply. I have to admit I'm still a bit disappointed though: I feel like quoting and concatenating arguments into a string which will then be parsed by the shell is an unnecessary burden... – ffevotte Sep 29 '14 at 18:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.