在pty下运行命令时出现双重回声

时间:2019-11-23 12:18:52

标签: c++ ssh tty 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(和换行符)后,child命令将我的输入重新发送到其自身的输出之前,从而复制了我的输入行:

~ $ 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的源代码。

1 个答案:

答案 0 :(得分:1)

正如您已经观察到的那样,在子级调用exec()之后,从属端的终端标志不再受您的控制,并且子级可以(并且经常)重新启用回显。这意味着在调用exec之前在子级中更改终端标志没有多大用处。

rlwraprlfe都以自己的方式(不同)解决了问题:

无论使用哪种方法,都必须知道您的输入是回显(在rlfe情况下还是在 (在rlwrap情况下)。 rlwrap至少是通过 not 在父进程中关闭pty的从属端,然后查看其终端设置(在这种情况下,其ECHOc_lflag)了解从站是否会回应。

这当然很麻烦。 rlfe方法可能更容易,因为它不需要使用readline库,并且您可以简单地strcmp()用刚刚发送的输入来接收接收到的输出(这只会在cat命令无法执行的情况下出错,该命令会禁用其输入的回声)