假设我打开了两个 shell,一个 PID 为 1234,另一个为 5678。
我可以清除屏幕并重置另一个 shell,echo $'\033c' > /proc/1234/fd/0
就像reset
在终端本身的提示符上键入一样。但是,当我echo $'\04' > /proc/1234/fd/0
发送 EOT 字符时,这似乎不起作用。怎样才能达到想要的效果呢?
答案1
首先,一个 tty,即使它看起来是一个文件对象,实际上也是一对管道/队列——一个用于输出,一个用于输入,每个管道/队列的另一端连接到某些硬件设备,或者在伪终端的情况下可供另一个程序使用。当您向链接到 tty 的 fd 写入任何内容时,您正在写入输出队列,即使那是进程的 fd 0 (stdin),并且不会循环回输入队列。
在大多数 Unix 系统上(值得注意的是开放BSD),有一个特殊的 ioctl ( TIOCSTI
),它允许您将一个字节插入到 tty 的输入队列中,就好像它是从另一端接收到的一样。TIOCSTI
仅当未在调用它的进程的控制 tty 上使用时,才会以 root 身份工作。
$ cc -Wall tiocsti.c -o tiocsti
-- 作为根用户 --
# printf '\04' | ./tiocsti >/proc/5460/fd/0
如果您的进程在伪 tty 中运行,“假”输入的另一种方法是使用调试器连接到持有伪 tty 的主端的进程,并write(2)
从那里将您的内容放入其中。
$ gdb -p pid_of_xterm
(gdb) p write(4, "\x04", 1) # 4 is the fd of the master pty
(gdb) c
两种方法都只是矫枉过正。如果您想控制正在运行的进程,最好将调试器附加到该进程,而不是伪造一些输入;如果你想工具化一个交互式程序,你最好运行它expect
;两者都可以在任何 Unix 系统上运行,并且不需要额外的权限。
另请注意,^D
/\004
只会起作用,EOF
就像A)tty 处于规范模式乙)c_cc[VEOF]
特殊字符没有更改为其他字符C)输出队列为空(否则c_cc[VEOF]
必须发送两次)。当然,只是从 tty 读取数据的进程将获得 EOF 或那些字节,而不一定是 shell。
tiocsti.c
#include <sys/ioctl.h>
#include <unistd.h>
#include <err.h>
int main(void){
char c; ssize_t r;
while((r = read(0, &c, 1)) > 0){
if(ioctl(1, TIOCSTI, &c)) err(1, "ioctl(TIOCSTI)");
}
if(r < 0) err(1, "read");
return 0;
}