22

I managed to do this

echo -n "command" > /dev/tty1

The letters appear, and the cursor moves, but they are "ghosts" – if you hit Enter, nothing happens (they are not in stdin).

Edit:

In the middle of the below screenshot, you see why I see the use of this. (The line with a red caption, right below the line with a yellow caption.) As it is now, you are not really "editing" the note text; you are just asked to write a new text, which will replace the text of the note you are (not really) editing. Thus, I thought it could be remedied by simply pasting the old text into the tty: if the user hits enter, no modification is made. (This program is in Perl/MySQL, but I thought it would be more interesting to ask for a general solution than "how do I do this in Perl".)

example

Edit 2:

Here is the Perl code, that uses the C code below (works exactly as intended), as well as a new screenshot – hopefully this will clarify things beyond doubt :) Again, look at the middle of the screenshot, where the edit is made to the note text - this time around, the old text is there, for example if you just wanted to fix a typo, you won't have to retype the entire note text.

my $edit_note_text = $edit_note_data[2];
print BOLD, RED, " new text: ", RESET;
system("writevt /dev/tty \"$edit_note_text\"");
my $new_text = <$in>;
$new_text = fix_input($new_text);
my $set_text = "UPDATE notes SET note = \"$new_text\" WHERE id = $edit_note_id";
$db->do($set_text);

better_example

Emanuel Berg
  • 6,903
  • 8
  • 44
  • 65

6 Answers6

12

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.

  • Made an edit to my post. Have a look and you'll see my thinking. – Emanuel Berg Sep 14 '12 at 17:22
  • @EmanuelBerg Your edit is hard to understand. Are you trying to programmatically feed input into a program that you're also using interactively? If that's what you want, run the program in screen or tmux and use their stuff (screen) or send-key (tmux) command or their paste buffer feature. – Gilles 'SO- stop being evil' Sep 14 '12 at 18:11
  • Made a second edit with the Perl code included - the invocation of the C binary is there. I don't know... as it was so simple (just one line of code) - is it really better to do it your way (with the screen or tmux tools)? – Emanuel Berg Sep 14 '12 at 23:34
  • @EmanuelBerg So yes, you're looking for screen -X stuff 'note version one'. – Gilles 'SO- stop being evil' Sep 14 '12 at 23:40
11

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.

11

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.

Totor
  • 20,040
  • Check out my second edit to the question. I already compiled the code I got from @htor - what I can see, it works great. Can you see any advantages using this code instead? (But thanks for your effort in either case.) – Emanuel Berg Sep 14 '12 at 23:38
  • Yes. See my recent edit. The point is to use the TIOCSTI ioctl. The code I gave does just that on file descriptor 0 (stdin). – Stéphane Chazelas Sep 15 '12 at 07:42
  • Not everyone has TIOCSTI any more. Five years after this answer was written, people started to drop it from kernels. https://unix.stackexchange.com/q/406690/5132 – JdeBP Feb 24 '20 at 09:10
  • Shorter Perl version: perl -e 'ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV;' – w00t Apr 07 '22 at 22:21
7

I just found a small C program called writevt that does the trick. Grab the source code here. To make it compile with gcc just remove the following lines first:

#include <lct/cline.h>
#include <lct/utils.h>

Update. The command is now part of console-tools, thus available in more recent systems, unless your distribution uses kbd instead of console-tools, in which case you can compile it from source (much more recent version, no modification needed).

Usage:

sudo writevt /dev/ttyN command 

Note that, for some reason, you have to use '\r' (or '\x0D') instead of '\n' (or '\x0A') to send a return.

PersianGulf
  • 10,850
0

Compile writevt.c: gcc writevt.c -o writevt

Then send commands by ./writevt /dev/ttyN 'ls -al^M'. ^M means return char, which is obtained by typing ctrl-v ctrl-m.

Names pipes and screen/tmux are also efficient methods, you may see here for details.

0

If you are running tmux you can use send-keys:

$ stty -echo; tmux send-keys test; stty echo

This will put test into your prompt (stty hides the echo).

laktak
  • 5,946