在 pty 下执行 运行 命令时双重回显
Double echo when running commands under a pty
我正在编写一个程序来创建一个 pty,然后派生并执行一个 ssh
命令,并将 pty 的从端作为其 stdin
。完整的源代码在这里。
using namespace std;
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = posix_openpt(O_RDWR);
grantpt(fd);
unlockpt(fd);
pid_t pid = fork();
if (pid == 0) { //slave
freopen(ptsname(fd), "r", stdin);
execlp("ssh", "ssh", "user@192.168.11.40", NULL);
} else { //master
FILE *f = fdopen(fd, "w");
string buf;
while (true) {
getline(cin, buf);
if (!cin) {
break;
}
fprintf(f, "%s\n", buf.c_str());
}
}
}
执行该程序并仅输入 echo hello
(和一个换行符)后,子命令在它自己的输出之前重新发送我的输入,从而复制我的输入行:
~ $ echo hello
echo hello #duplication
hello
~ $
我认为这是因为 pty 的行为几乎与普通终端相同。如果我添加 freopen("log.txt", "w", stdout);"
并输入相同的命令,我只会得到
echo hello #This is printed because I typed it.
而log.txt
的内容是这样的:
~ $ echo hello #I think this is printed because a pty simulates input.
hello
~ $
如何避免重复?
可以实现吗?
我知道它在某种程度上是可以实现的,但不知道如何实现。事实上,rlwrap
命令的行为与我的程序相同,只是它没有任何重复:
~/somedir $ rlwrap ssh user@192.168.11.40
~ $ echo hello
hello
~ $
我正在阅读rlwrap
的源代码,但还没有理解它的实现。
补充
如 this question 中所建议(对我来说,不是答案,但 OP 很有帮助。),取消设置 ECHO
终端标志会禁用双重回显。在我的例子中,将这个片段添加到从块解决了这个问题。
termios terminal_attribute;
int fd_slave = fileno(fopen(ptsname(fd_master), "r"));
tcgetattr(fd_slave, &terminal_attribute);
terminal_attribute.c_lflag &= ~ECHO;
tcsetattr(fd_slave, TCSANOW, &terminal_attribute);
需要注意的是,这不是rlwrap
所做的。据我测试,rlwrap <command>
从不重复任何 <command>
的输入行,但是,我的程序对某些 <command>
回显两次。例如,
~ $ echo hello
hello #no duplication
~ $ /usr/bin/wolfram
Mathematica 12.0.1 Kernel for Linux ARM (32-bit)
Copyright 1988-2019 Wolfram Research, Inc.
In[1]:= 3 + 4
3 + 4 #duplication (my program makes this while `rlwrap` doesn't)
Out[1]= 7
In[2]:=
这是因为 <command>
(ssh
当我 运行 wolfram
远程)重新启用回显吗?无论如何,我应该继续阅读 rlwrap
.
的源代码
正如您已经观察到的,在 child 调用 exec()
之后,从端的终端标志不再受您的控制,并且 child 可能(并且经常会) ) re-enable 回声。这意味着在调用 exec
.
之前更改 child 中的终端标志没有多大用处
两者 rlwrap and rlfe 都以自己的(不同)方式解决问题:
rlfe
保留输入的行,但 removes the echo'ed input 在显示 child 的输出之前
rlwrap
removes the entered line 并用 echo 代替
无论您使用什么方法,您都必须知道您的输入是(在 rlfe
的情况下)还是 将(在 rlwrap
的情况下)案例)回应。 rlwrap
,至少,通过 not 在 parent 进程中关闭 pty 的从端,然后观察其终端设置(在本例中,ECHO
位在其 c_lflag
) 中以了解从机是否回显。
当然,所有这些都相当麻烦。 rlfe
方法可能更简单,因为它不需要使用 readline
库,你可以简单地 strcmp()
接收到的输出和你刚刚发送的输入(这只会在 cat
命令禁用其输入回显的不太可能的情况下出错)
我正在编写一个程序来创建一个 pty,然后派生并执行一个 ssh
命令,并将 pty 的从端作为其 stdin
。完整的源代码在这里。
using namespace std;
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = posix_openpt(O_RDWR);
grantpt(fd);
unlockpt(fd);
pid_t pid = fork();
if (pid == 0) { //slave
freopen(ptsname(fd), "r", stdin);
execlp("ssh", "ssh", "user@192.168.11.40", NULL);
} else { //master
FILE *f = fdopen(fd, "w");
string buf;
while (true) {
getline(cin, buf);
if (!cin) {
break;
}
fprintf(f, "%s\n", buf.c_str());
}
}
}
执行该程序并仅输入 echo hello
(和一个换行符)后,子命令在它自己的输出之前重新发送我的输入,从而复制我的输入行:
~ $ echo hello
echo hello #duplication
hello
~ $
我认为这是因为 pty 的行为几乎与普通终端相同。如果我添加 freopen("log.txt", "w", stdout);"
并输入相同的命令,我只会得到
echo hello #This is printed because I typed it.
而log.txt
的内容是这样的:
~ $ echo hello #I think this is printed because a pty simulates input.
hello
~ $
如何避免重复?
可以实现吗?
我知道它在某种程度上是可以实现的,但不知道如何实现。事实上,rlwrap
命令的行为与我的程序相同,只是它没有任何重复:
~/somedir $ rlwrap ssh user@192.168.11.40
~ $ echo hello
hello
~ $
我正在阅读rlwrap
的源代码,但还没有理解它的实现。
补充
如 this question 中所建议(对我来说,不是答案,但 OP 很有帮助。),取消设置 ECHO
终端标志会禁用双重回显。在我的例子中,将这个片段添加到从块解决了这个问题。
termios terminal_attribute;
int fd_slave = fileno(fopen(ptsname(fd_master), "r"));
tcgetattr(fd_slave, &terminal_attribute);
terminal_attribute.c_lflag &= ~ECHO;
tcsetattr(fd_slave, TCSANOW, &terminal_attribute);
需要注意的是,这不是rlwrap
所做的。据我测试,rlwrap <command>
从不重复任何 <command>
的输入行,但是,我的程序对某些 <command>
回显两次。例如,
~ $ echo hello
hello #no duplication
~ $ /usr/bin/wolfram
Mathematica 12.0.1 Kernel for Linux ARM (32-bit)
Copyright 1988-2019 Wolfram Research, Inc.
In[1]:= 3 + 4
3 + 4 #duplication (my program makes this while `rlwrap` doesn't)
Out[1]= 7
In[2]:=
这是因为 <command>
(ssh
当我 运行 wolfram
远程)重新启用回显吗?无论如何,我应该继续阅读 rlwrap
.
正如您已经观察到的,在 child 调用 exec()
之后,从端的终端标志不再受您的控制,并且 child 可能(并且经常会) ) re-enable 回声。这意味着在调用 exec
.
两者 rlwrap and rlfe 都以自己的(不同)方式解决问题:
rlfe
保留输入的行,但 removes the echo'ed input 在显示 child 的输出之前rlwrap
removes the entered line 并用 echo 代替
无论您使用什么方法,您都必须知道您的输入是(在 rlfe
的情况下)还是 将(在 rlwrap
的情况下)案例)回应。 rlwrap
,至少,通过 not 在 parent 进程中关闭 pty 的从端,然后观察其终端设置(在本例中,ECHO
位在其 c_lflag
) 中以了解从机是否回显。
当然,所有这些都相当麻烦。 rlfe
方法可能更简单,因为它不需要使用 readline
库,你可以简单地 strcmp()
接收到的输出和你刚刚发送的输入(这只会在 cat
命令禁用其输入回显的不太可能的情况下出错)