15

I'm writing a program that reads input from stdin, manipulates the input, and writes output to stdout. However, many programs check whether stdin is a terminal or a pipe (by calling a function like isatty), and generate output differently. How do I have my program pretend to be a TTY?

The solution should work on both Linux and macOS. Any programming language that generates a standalone binary is acceptable, but Go is preferred.

Note that I'm asking a programming question, not asking for a tool. So, things like script or unbuffer is not something I'm looking for.

rphv
  • 5,409
  • 3
  • 29
  • 47
Ethan
  • 18,584
  • 15
  • 51
  • 72
  • There are interesting approaches over there: http://unix.stackexchange.com/a/249764/65275 – coredump Oct 16 '16 at 07:26
  • Thanks but all those solutions depend on certain external programs. I need to write a standalone program that works without requiring the client system to have certain things installed. – Ethan Oct 16 '16 at 07:29
  • 3
    [This Unix & Linux Stack Exchange question](http://unix.stackexchange.com/q/21147/12106) might be more relevant. The term you really want to search for is *pseudo tty* (or *pty*). – Some programmer dude Oct 16 '16 at 07:35
  • 2
    I know but correct me if I'm wrong. Unix Stacke Exchange is for unix *tools*, not for programming questions. – Ethan Oct 16 '16 at 07:37
  • 1
    https://stackoverflow.com/questions/8374000/how-to-use-pseudo-terminals-in-linux-with-c and https://stackoverflow.com/questions/476354/how-do-nix-pseudo-terminals-work-whats-the-master-slave-channel with some code examples. Could you provide a little more information about what you want to achieve? – coredump Oct 16 '16 at 07:44
  • 2
    The purpose is to preserve color codes read from any program. – Ethan Oct 16 '16 at 07:47
  • 1
    True about the U&L SE, but the first point in the accepted answer will give you hints enough to help you get going ([reading the `pty` manual page](http://man7.org/linux/man-pages/man7/pty.7.html)). – Some programmer dude Oct 16 '16 at 08:12
  • If I recall correctly, `less -R` does preserve the color codes. Using `strace` to analyze the syscalls it employs might gives you some hints. – user1937358 Oct 16 '16 at 14:10
  • 2
    Maybe this: https://github.com/kr/pty? – coredump Oct 16 '16 at 14:15
  • I found [example code](http://man7.org/tlpi/code/online/dist/pty/unbuffer.c.html) for a program that's similar to [UNBUFFER(1)](http://linuxcommand.org/man_pages/unbuffer1.html). Works very well! – Ethan Oct 17 '16 at 00:13

1 Answers1

3

The following is fully working code for running a command in a pty and capturing its output. (Not as many lines as you may have thought.)

#include <signal.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>
#include <util.h>

pid_t child = 0;

void sighandler(int signum) {
  if (child > 0) {
    killpg(child, signum);
    exit(signum);
  }
}

// Run a command in a pty.
// Usage: /path/to/this/binary command to run
int main(int argc, char *argv[]) {
  if (argc < 2) {
    return EX_USAGE;
  }

  int master;
  child = forkpty(&master, NULL, NULL, NULL);

  if (child == -1) {
    perror("failed to fork pty");
    return EX_OSERR;
  }

  if (child == 0) {
    // we're in the child process, so replace it with the command
    execvp(argv[1], argv + 1);
    perror("failed to execute command");
    return EX_OSERR;
  }

  // trap kill signals and forward them to child process
  signal(SIGHUP, sighandler);
  signal(SIGINT, sighandler);
  signal(SIGTERM, sighandler);

  const int buf_size = 1024;
  char buf[buf_size];
  fd_set fds;
  ssize_t bytes_read;

  // forward the output continuously
  while (1) {
    FD_ZERO(&fds);
    FD_SET(master, &fds);

    if (select(master + 1, &fds, NULL, NULL, NULL) > 0 && FD_ISSET(master, &fds)) {
      bytes_read = read(master, buf, buf_size);
      if (bytes_read <= 0) {
        return EXIT_SUCCESS;
      }

      if (write(STDOUT_FILENO, buf, bytes_read) != bytes_read) {
        perror("failed to write to stdout");
        return EX_OSERR;
      }
    }
  }
}
Tim
  • 41,901
  • 18
  • 127
  • 145
Ethan
  • 18,584
  • 15
  • 51
  • 72
  • To make this compile I need `#include #include ` and remove `#include`. // side note, to interact with applications with a TUI, it will usually be necessary to set the terminal to raw mode in the parent process, see stackoverflow.com/a/30632435/5267751 for an example. – user202729 Feb 22 '23 at 14:56