This may be an XY problem, but I would like to clone a serial terminal such as /dev/ttyUSB0 into two terminals. The input from either muxed into the real terminal, and the output fed to both. Is there a tool to achieve this/is this a possibility?

I often work with embedded hardware over serial using a small terminal emulator dterm. I would like to keep that interactive terminal open at all times, but also run command sequences using expect on it. Say, to reboot and stop at the U-Boot prompt for example.

I imagine a tool to be used like this:

$ teeterm /dev/ttyUSB0
Clone successful: /dev/pts/3 and /dev/pts/4 available.
$ dterm /dev/pts/3 115200

And in another shell: $ ./uboot (which spawns and interacts with dterm /dev/pts/4 115200)

  • use screen – Ipor Sircer Feb 20 '17 at 2:07
  • 1
    I have a small C program that implements a bi-directional pipe between two newly created pseudo-terminals that could be easily adapted to your problem (too lazy to do the adaption myself). Would that help you? – dirkt Feb 20 '17 at 8:01
  • @dirkt That would absolutely help, is the source open and online somewhere? – kcghost Feb 20 '17 at 12:33

As requested, a small C program that bi-directional pipe between two newly created pseudo-terminals. No, that doesn't answer the question, but it can be easily adapted to do so. It's not very long, so here's the code. Licence is "do whatever you want with it".

/* ptycat (ptypipe? ptypair?)
 * create a pair of pseudo-terminal slaves connected to each other
 * Link with -lutil
 * Alternative: socat PTY,link=COM8 PTY,link=COM9

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <pty.h>

#undef max
#define max(x,y) ((x) > (y) ? (x) : (y))

  (void)ioctl(STDIN_FILENO, TIOCGWINSZ, &win);


/* TODO: make symlinks, unlink on atexit */

static uint8_t buf[BUFSIZ]; /* BUFSIZ from stdio.h, at least 256 */
static char *log_dir = NULL;

void logdata (char *dir, uint8_t *data, int n) {
  if (dir != log_dir) fprintf (stdout, "\n%s", dir);
  log_dir = dir;
  for (; n > 0; n--, data++) fprintf (stdout, " %02x", *data);
  fflush (stdout);

int main (int argc, char* argv[])
  char name[256]; /* max namelen = 255 for most fs. */
  fd_set rfd;

  struct termios tt;
  struct winsize ws;
  int master[2], slave[2];
  int n, nfds, cc;

  if (tcgetattr (STDIN_FILENO, &tt) < 0)
    perror("Cannot get terminal attributes of stdin");
  cfmakeraw (&tt);
  for (int i = 0; i < 2; i++)
    if (openpty (&master[i], &slave[i], name, &tt, NULL /*ws*/) < 0)
      perror("Cannot open pty");

  for (;;) {
    FD_SET(master[0], &rfd);
    FD_SET(master[1], &rfd);
    nfds = max(master[0], master[1]) + 1;
    n = select(nfds, &rfd, 0, 0, NULL);
    if (n > 0 || errno == EINTR)
      if (FD_ISSET(master[0], &rfd))
        if ((cc = read(master[0], buf, sizeof(buf))) > 0)
          (void) write(master[1], buf, cc);
          logdata (">>>", buf, cc);

      if (FD_ISSET(master[1], &rfd))
        if ((cc = read(master[1], buf, sizeof(buf))) > 0)
          (void) write(master[0], buf, cc);
          logdata ("<<<", buf, cc);
  /* This is never reached */
  return 0; 

Using dirkt's posted example code as a base, I put together a real utility for this. It is available on Github.

I ended up on a usage of: teeterm dterm /dev/ttyUSB0 115200 (in another shell) dterm pty0 (in another shell) dterm pty1

The two pseudoterminals both have access to the command process I/O. The most important aspects of the utility are a forkpty call to control a child process via a pseudoterminal (that is not made available to the user), along with openpty and select calls to create the two pseudoterminals and monitor all three for inputs.

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.