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

2
  • I did this in Python over on Stack Overflow if you are interested. stackoverflow.com/a/29616465/117471 Commented Feb 16, 2017 at 20:31
  • Your problem statement is not clear. What is the problem?
    – user56041
    Commented Jun 4, 2017 at 5:00

6 Answers 6

13

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.

4
  • Made an edit to my post. Have a look and you'll see my thinking. Commented Sep 14, 2012 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. Commented Sep 14, 2012 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)? Commented Sep 14, 2012 at 23:34
  • @EmanuelBerg So yes, you're looking for screen -X stuff 'note version one'. Commented Sep 14, 2012 at 23:40
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.

4
  • 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.) Commented Sep 14, 2012 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). Commented Sep 15, 2012 at 7:42
  • Not everyone has TIOCSTI any more. Five years after this answer was written, people started to drop it from kernels. unix.stackexchange.com/q/406690/5132
    – JdeBP
    Commented Feb 24, 2020 at 9:10
  • Shorter Perl version: perl -e 'ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV;'
    – w00t
    Commented Apr 7, 2022 at 22:21
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.

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.

4
  • This does work, but there's way more wrong than just those includes. I had to ditch the usage function, make a progname and _, and comment out a few function calls in main() Commented Sep 13, 2012 at 22:45
  • @MichaelMrozek The _() function is usually a sign of gettext being used. Seems a bit overkill for such a simple piece of demo code but doesn't hurt I suppose.
    – jw013
    Commented Sep 13, 2012 at 22:55
  • The link in the above answer is broken.  I found another writevt.c here (at github.com/ grawity); it appears to be essentially the same program. Commented Oct 29, 2018 at 16:29
  • 1
    Does not work for me - only prints command. Dending \r or \n prinst r n resperctively for whatever reasson ;/ Commented Aug 21, 2019 at 8:22
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).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .