Skip to content

Instantly share code, notes, and snippets.

@johnlane
Created July 10, 2015 10:34
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnlane/6bdeac9dd5a7b0c60820 to your computer and use it in GitHub Desktop.
Save johnlane/6bdeac9dd5a7b0c60820 to your computer and use it in GitHub Desktop.

Injecting Terminal Input

Some examples demonstrating how to inject keystrokes into a terminal's input stream.

#!/bin/bash
bind '"\e[0n": "ls -l"'; printf '\e[5n'
#include <stdlib.h>
#include <argp.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
const char *argp_program_version ="inject - see http://unix.stackexchange.com/q/213799";
static char doc[] = "inject - write to terminal input stream";
static struct argp_option options[] = {
{ "tty", 't', "TTY", 0, "target tty (defaults to current)"},
{ "nonl", 'n', 0, 0, "do not output the trailing newline"},
{ 0 }
};
struct arguments
{
int fd, nl, next;
};
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
struct arguments *arguments = state->input;
switch (key)
{
case 't': arguments->fd = open(arg, O_WRONLY|O_NONBLOCK);
if (arguments->fd > 0)
break;
else
return EINVAL;
case 'n': arguments->nl = 0; break;
case ARGP_KEY_ARGS: arguments->next = state->next; return 0;
default: return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = { options, parse_opt, 0, doc };
static struct arguments arguments;
static void inject(char c)
{
ioctl(arguments.fd, TIOCSTI, &c);
}
int main(int argc, char *argv[])
{
arguments.fd=0;
arguments.nl='\n';
if (argp_parse (&argp, argc, argv, 0, 0, &arguments))
{
perror("Error");
exit(errno);
}
int a,c;
for (a=arguments.next, c=0; a< argc; c=0 )
{
while (argv[a][c])
inject (argv[a][c++]);
if (++a < argc) inject(' ');
}
if (arguments.nl) inject(arguments.nl);
return 0;
}
#!/usr/bin/perl
require "sys/ioctl.ph";
ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV
#!/usr/bin/python
import fcntl, sys, termios
del sys.argv[0]
for c in ' '.join(sys.argv):
fcntl.ioctl(sys.stdin, termios.TIOCSTI, c)
#!/usr/bin/ruby
ARGV.join(' ').split('').each { |c| $stdin.ioctl(0x5412,c) }
#!/bin/bash
# Example wrapper around injected terminal input to
# prevent premature echo in front of the prompt.
# A more basic solution is to have the prompt pre-clear its line
# which can be done by prefixing it with "\r\e[M"
inject() {
if [[ $0 == "$BASH_SOURCE" ]] # executed or sourced ?
then
# ioctl TIOCSTI method works best (it works if sourced or executed)
echo using ioctl method
perl -e 'ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV' "$@" " "
else
# escape method only works if script is sourced
echo using bind method
bind "\"\e[0n\": '$*'"; printf '\e[5n'
fi
}
saved_settings=$(stty -g)
stty -echo -icanon min 1 time 0
inject echo $*
until read -t0; do
sleep 0.02
done
stty "$saved_settings"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment