I need to send keystrokes virtually to a terminal program (like vi or emacs).

I want to do something like this:

echo -e 'iHello, world!\e' | vi

and then have a vi session open with this buffer:

Hello, world!

But that does not work as vi does not read keystrokes through stdin.

I get this error:

ex/vi: Vi's standard input and output must be a terminal

How can I send some text string to a terminal program as if the string was typed directly on a keyboard?

  • You could use expect for this, as the most general case, but that's often a pain. It may be preferable to write a more specific script using shell. – Tom Hunt Oct 23 '15 at 20:13

That's typically what expect was written for:

expect -c 'spawn -noecho vi; send "iHello World!\r\33"; interact'

While expect was written for TCL in days prior to perl or python being popular, now similar modules for perl or python are also available.

Another option is to issue TIOCSTI ioctls to your tty device to insert characters (one byte at a time) in its input queue:

perl -le 'require "sys/ioctl.ph";
          ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV
         ' $'iHello World!\r\e'; vi

That has the benefit of avoiding an extra pseudo-terminal layer in between your terminal emulator and the application (here vi).

  • There's a typo: TIOCTSI in the flowing text, should be TIOCSTI as in the code snippet. (Edits must be at least 6 characters, why stackoverflow, oh why?) – egmont Nov 24 '17 at 9:25
  • 1
    I've just found out that OpenBSD has recently removed TIOCSTI support due to security concerns: undeadly.org/cgi?action=article&sid=20170701132619. – egmont Nov 24 '17 at 9:49
  • 1
    @egmont, thanks. I didn't known that. It's true it can be abused across setuid/setgid, but IMO, the problem there is setuid/setgid executables, not so much the TIOCSTI ioctl (which can be very useful, you'll find many Q&A mentioning it here). The notion of changing uid whilst keeping the same environment (environ, fds, tty, limits, signal disposition, umask...) is wrong. – Stéphane Chazelas Nov 24 '17 at 19:57
  • I agree with you that the core of the security issue lies somewhere else, and if properly addressed there, TIOCSTI should be harmless. But we also live in an imperfect world where eliminating such relatively rarely used corner case pain points does increase security. In the mean time I don't like TIOCSTI at all, I'm much more in favor of the expect-like approach. One kernel call per byte, not being able to pass a string in a single step, and not being able to use standard tools like echo or cat, doesn't sound right to me. – egmont Nov 24 '17 at 21:48

Building on @Stéphane Chazelas' Perl example, the following is practical from a script or command line:

stty -echo; perl -le 'require "sys/ioctl.ph"; ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV ' `_cmds_` ;stty echo; _app_

If you drop the final app the output of cmds will be present on the input queue (as it is called in the kernel), on your current console shell or in the application that shelled the command line. The stty just stops any (full-duplex) echo to the screen.

Note: technically, this is not explicitly "simulating keypress".

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.