Construct a command by putting a string into a tty

A terminal doubles as two things: an input device (such as a keyboard) and a display device (such as a monitor). When you read from the terminal, you get what comes from the input device. When you write to the terminal, the data goes onto the display device.

There is no general way of forcing input into a terminal. There is rarely any need to do so. If you need to interact with a program that requires a terminal, use a dedicated terminal emulator such as Expect or Empty, or a programmable terminal wrapper such as Screen or Tmux. You can force input into a Linux console with an ioctl. You can force input into an X11 terminal emulator with tools such as xdotool or xmacro.


At least Linux and BSDs have the TIOCSTI ioctl to push characters back to the terminal input buffer (up to a limit [4096 characters on Linux]):

#include <sys/ioctl.h>
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>

void stackchar(char c)
{
  if (ioctl(0, TIOCSTI, &c) < 0) {
    perror("ioctl");
    exit(1);
  }
}
int main(int argc, char *argv[])
{
  int i, j;
  char c;

  for (i = 1; i < argc; i++) {
    if (i > 1) stackchar(' ');
    for (j=0; (c = argv[i][j]); j++) {
      stackchar(c);
    }
  }
  exit(0);
}

Compile it, and call it as:

cmd foo bar < "$some_tty"

to push characters back on some tty.

And in perl:

require "sys/ioctl.ph";
ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV;

Edit: I realise now it's the same ioctl as in the writevt solution. The comment and the name of the command is misleading as TIOCSTI works for any terminal, not just VTs.


I have a more complete demo over on Stack Overflow.

In python you can do:

import fcntl
import sys
import termios

with open('/dev/tty1', 'w') as fd:
    for char in "ls -la\n":
        fcntl.ioctl(fd, termios.TIOCSTI, char)

That's assuming a simple "command" value of ls -la and using the tty path specified by OP.

Tags:

Io

Stdin

Tty