6

I want to use emacsclient to let my running instance of emacs evaluate a function and print the result to a terminal.

That is, I want to do something like this:

$ emacsclient --eval "(frame-parameter (car (frame-list)) 'name)"
*info*

However, instead, I get output like this:

"*info*"

If I use message, the output is displayed in some frame's minibuffer and I still get quoted output ("*info*" instead of *info*).

How do I get princ-like output to be output to my emacsclient terminal?

2

2 Answers 2

7

It seems that emacsclient is the wrong way to do what I want. I thought I could not use emacs --batch because I wanted to get "live" info from my running emacs instance.

The missing piece of the puzzle was the function server-eval-at:

$ emacs --batch --eval "(progn (require 'server) (princ (format \"%s\\n\" (server-eval-at \"server\" '(frame-parameter (car (frame-list)) 'name)))))"
*info*

Or, a more significant example:

$ emacs --batch --eval "(progn (require 'server) (princ (format \"%s\\n\" (server-eval-at \"server\" '(mapconcat (lambda (f) (frame-parameter f 'name)) (frame-list) \"\\n\")))))"
*info*
CAPTURE-3-foo-service.org
SomeClass.java
SomeOtherClass.java
0
0

AFAIK it is not possible to redirect standard output from an emacs server directly with emacsclient alone. As you found, the only output of is the value that the s-expression evaluates to. server-eval-at with emacs --batch --eval looks like a good option. If you still want to use emacsclient for some reason, here's one approach that will work for capturing standard output. I'm not sure how to capture standard error in this manner.

Since emacsclient --eval does not capture any of the server's standard output streams, you will need to capture it as part of your s-expression:

emacsclient --eval "(with-output-to-string\
  (mapc\
   (lambda (frame)\
     (princ (format \"%s\\n\" (frame-parameter frame 'name))))\
   (frame-list)))"

Anything printed to standard output during within the body of with-output-to-string is captured and returned as a string value. You can then print that string output as Emacs otherwise (by using Emacs to interpret it):

emacsclient --eval '(with-output-to-string \
  (mapc \
   (lambda (frame) \
     (princ (format "%s\\n" (frame-parameter frame \'name)))) \
   (frame-list)))' \
| emacs --batch --eval '(princ \
  (concat \
    (with-temp-buffer \
      (insert-file-contents "/dev/stdin") \
      (read (current-buffer))) \
    "\n"))'

The same command (in one line):

emacsclient --eval '(with-output-to-string (princ "Hello, World!"))' | emacs --batch --eval '(princ (concat (with-temp-buffer (insert-file-contents "/dev/stdin") (read (current-buffer))) "\n"))'
5
  • Thanks! I am using emacsclient because I want to get information about my running emacs process. epipe might be a good solution. I'm not sure make-symbol is right for what I want. Imagine I want output like "You have 37 buffers:\n- 2 are elisp-mode\n- 2 are info-mode\n...". Commented Nov 15, 2016 at 15:33
  • Would using server-eval-at within the same server that emacsclient return the same or different information?
    – grettke
    Commented Apr 1, 2021 at 23:23
  • 2
    @grettke you can use both emacsclient --eval and server-eval-at to invoke an s-expression in a particular emacs server. Note my answer about how standard output and standard error are not printed by emacsclient --eval.
    – ebpa
    Commented Apr 4, 2021 at 0:05
  • 1
    Thank you @ebpa.
    – grettke
    Commented Apr 4, 2021 at 23:25
  • When I use the example above with Emacs 29, piping to emacs --batch ... to unquote, I get Maximum buffer size exceeded. I have to set the END argument of insert-file-contents to some reasonable value to make it work. I have written up a bit more in reddit.com/r/emacs/comments/asil1y/comment/jm3r2a6 -- hope that helps someone, but also grateful for any more elegant solutions. Commented May 29, 2023 at 18:58

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