5

I want to be able to tell whether stdout is a pipe in an elisp script. This would allow to distinguish between:

$ emacs --script foo.el

and

$ emacs --script foo.el | cat

Is this possible in Emacs lisp? Most programming languages have this in the stdlib.

Drew
  • 75,699
  • 9
  • 109
  • 225
Wilfred Hughes
  • 6,890
  • 2
  • 29
  • 59

3 Answers3

3

Wrapping isatty(3) in a dynamic module seems to work:

$ emacs --batch -Q --load isatty.so --eval '(princ  (isatty))'
t

$ emacs --batch -Q --load isatty.so --eval '(princ  (isatty))' | cat
nil

Build the module

$ EMACS_ROOT=/home/xcy/src/emacs/ # /path/to/root/of/emacs/source/code
$ gcc -I$EMACS_ROOT/src -fPIC -c isatty.c
$ gcc -shared isatty.o -o isatty.so

isatty.c's contents:

#include <emacs-module.h>
#include <stdio.h>
#include <unistd.h>

int plugin_is_GPL_compatible;

static emacs_value
Fisatty (emacs_env *env, int argc, emacs_env argv[], void *data)
{
  if (isatty (fileno (stdout)))
    return env->intern (env, "t");
  else
    return env->intern (env, "nil");
}

static void
bind_function (emacs_env *env, const char *name, emacs_value Sfun)
{
  emacs_value Qfset = env->intern (env, "fset");
  emacs_value Qsym = env->intern (env, name);

  emacs_value args[] = {Qsym, Sfun};

  env->funcall (env, Qfset, 2, args);
}

int emacs_module_init (struct emacs_runtime *ert)
{
  emacs_env *env = ert->get_environment (ert);
  emacs_value fun = env->make_function (env,
                                        0,
                                        0,
                                        Fisatty,
                                        "Return t if stdout is tty, nil otherwise.",
                                        NULL);
  bind_function (env, "isatty", fun);
  return 0;
}
xuchunyang
  • 14,302
  • 1
  • 18
  • 39
2

There is a work-around by wrapping Emacs in a subshell:

$ ( [ -t 1 ] && export TTY_P=yes ; emacs ... )

For example, the output is a tty:

$ ( [ -t 1 ] && export TTY_P=yes ; emacs --batch --eval '(princ (getenv "TTY_P"))' )

yes

and the output is not a tty (piped or redirected)

$ ( [ -t 1 ] && export TTY_P=yes ; emacs --batch --eval '(princ (getenv "TTY_P"))' ) | cat

nil
xuchunyang
  • 14,302
  • 1
  • 18
  • 39
1

On Linux, you can use /proc:

(let* ((stdout-proc-path
        (format "/proc/%s/fd/1" (emacs-pid)))
       (piped-p
        ;; If following the links gives us a path like
        ;; "/proc/26783/fd/pipe:[432241]" (which doesn't exist) rather than
        ;; "/dev/pts/2" (which exists), then we're piped into another
        ;; process. Assumes Emacs is not running in a GUI.
        (not (file-exists-p (file-chase-links stdout-proc-path)))))
  (princ (format "have we been piped? %s\n" piped-p)))

However, this isn't portable: not even OS X has /proc. Hopefully there are better alternatives.

Wilfred Hughes
  • 6,890
  • 2
  • 29
  • 59