I have been working on a solution to mirror the PWD of multiple terminals. As part of that I need a way to execute commands on other tty/pts. A simple echo won’t work because echo writes to the output buffer, while I need to push these commands to the input buffer of the tty/pts. I found this forum post after some googling. I adapted the code from the forum post for my purpose.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>
void print_help(char *prog_name) {
printf("Usage: %s [-n] DEVNAME COMMAND\n", prog_name);
printf("Usage: '-n' is an optional argument if you want to push a new line at the end of the text\n");
printf("Usage: Will require 'sudo' to run if the executable is not setuid root\n");
exit(1);
}
int main (int argc, char *argv[]) {
char *cmd, *nl = "\n";
int i, fd;
int devno, commandno, newline;
int mem_len;
devno = 1; commandno = 2; newline = 0;
if (argc < 3) {
print_help(argv[0]);
}
if (argc > 3 && argv[1][0] == '-' && argv[1][1] == 'n') {
devno = 2; commandno = 3; newline=1;
} else if (argc > 3 && argv[1][0] == '-' && argv[1][1] != 'n') {
printf("Invalid Option\n");
print_help(argv[0]);
}
fd = open(argv[devno],O_RDWR);
if(fd == -1) {
perror("open DEVICE");
exit(1);
}
mem_len = 0;
for ( i = commandno; i < argc; i++ ) {
mem_len += strlen(argv[i]) + 2;
if ( i > commandno ) {
cmd = (char *)realloc((void *)cmd, mem_len);
} else { //i == commandno
cmd = (char *)malloc(mem_len);
}
strcat(cmd, argv[i]);
strcat(cmd, " ");
}
if (newline == 0)
usleep(225000);
for (i = 0; cmd[i]; i++)
ioctl (fd, TIOCSTI, cmd+i);
if (newline == 1)
ioctl (fd, TIOCSTI, nl);
close(fd);
free((void *)cmd);
exit (0);
}
Copy the above code to some C file (For eg. ttyecho.c). Run the following command in the directory you have created the C file in to compile the code.
make ttyecho
If you named the file as abc.c then the command would be make abc.
Copy this file to the bin directory under your Home Directory. In my case it is /home/pratik/bin. Create the directory if it doesn’t exist. Its a good practice to keep all custom binaries/executables in this bin directory.
Start another terminal or switch to any other open terminal that you wish to control and execute the command tty. You can see a sample output below.
@~$ tty
/dev/pts/5
Now to execute a command on /dev/pts/5, run the following command in the controlling/original terminal.
sudo ttyecho -n /dev/pts/5 ls
You will see that the ls command is executed in /dev/pts/5. The -n option makes ttyecho send a newline after the command, so that the command gets executed and not just inserted. This utility can infact be used to send any data to other terminals For eg, you could open vim in /dev/pts/5 and then run the following command in the controlling terminal to cause vim to exit in /dev/pts/5.
sudo ttyecho -n /dev/pts/5 :q
To avoid using sudo all the time, so that the command is easily scriptable, change the owners/permissions of this executable using the following commands.
sudo chown root:root ttyecho sudo chmod u+s ttyecho
What we did was change the owner/group to root and set the setuid bit for the executable which will allow you to run the utility with root permissions.
Thanks a lot! Saved my day!
I had a disconnected SSH connection to a server, and in that SSH connection, the mail tool was running with an open overfilled mbox with more than 900.000 mails. Just opening this big mbox took quite a while. Killing the mail process and re-opening that mbox again would’ve been a pain in the a**.
As I only needed the last ~~ 100 of these mails, I used ttyecho to send “d 1-900000″ and “q”, so the mail utility deleted most of the mails and finished nicely, leaving only a few hundred messages for later reading & filtering.
This worked perfectly, it took just a minute, and that includes the time needed to create ttyecho.c, copy&paste and compiling.
thanks for the code!!
Wow! Like others said, this tool is truly a gem. So many people states that writing to the stdin of an unrelated process isn’t possible…
Thanks to your need util, I didn’t had to restart a long taking process, that had already finished, but asked me to answer 17272 times “1″. I’m going to include it on all my installations from now on, it is so useful.
Thanks a lot.
Thanks! Ive been looking for this solution for a while.
Fantastic! I was poking around all day trying to figure out how to do with tee. Saved me a lot of frustration! Thank you kind sir.
Very useful program. Thank you very much
Great tool. I have been searching for a solution to this for a while and always running into dead ends. Nice clean solution to executing commands in other terminals. This is a real gem piece of code.
Thanks!
that tool just rocks dynamite!
I’ve written an initramfs generator (http://github.com/r1k0/kigen) supporting dropbear (amongst others) to rescue LUKS systems remotly and I had a very hard time firing up the new init from a remote dropbear console (/dev/ttyp0).
Thanks to your tool and I can redirect my commands to /dev/console (as if it was local).
very very nice
Thanks for the fantastic tool!
I’m looking for the solution for days.
Learned from your code