(6)通过非交互的方式驱动交互程序
尽管我们觉得pty可以运行任何协作处理进程(甚至一个协作处理进程是交互的进程),它并不会正常工作。问题是,pty只是仅仅将它的标准输入的内容拷贝到PTY,以及将PTY的所有内容拷贝到它的标准输出,却不会检查它究竟发送了什么以及获取了什么。
作为一个例子,我们可以在pty下面运行telnet命令直接与远程主机交互:
pty telnet 192.168.1.3
这样做,并不比直接键入telnet 192.168.1.3好多少,但是我们可能想要telnet程序从一个脚本中运行,并且可能会检测远程主机上面的一些条件。如果文件telnet.cmd包含这四行:
sar
passwd
uptime
exit
其中第一行远程登陆的用户名称,第二行是密码,第三行是我们想要运行的命令,第四行是终止会话的行。但是如果我们如下运行这个脚本:
pty -i < telnet.cmd telnet 192.168.1.3
它并没有如我们所想的方式工作。所发生的事情应就是,文件telnet.cmd中的内容在开始提示我们输入用户名称和密码之前就被发送到远程主机上面。当关闭回显读取密码的时候,login程序使用tcsetattr选项,导致所有已经被排队的数据被丢弃。因此,我们发送的数据被丢弃了。
当我们交互运行telnet程序的时候,我们等待远程主机提示密码,然后我们才开始输入。但是pty程序不知道需要这样做,所以导致前面所说的那个现象。因此,需要一个更复杂的程序来实现这个功能,例如expect程序,通过这个程序才能正常地从一个脚本中以非交互的方式来运行一个交互的程序。
即使运行前面我们展示的"使用伪终端来驱动一个协作处理进程(扩展之前)"对应的程序,也没有什么作用。因为,程序假设每向一个管道写一行,也会在另外一个管道产生同样的一行。在交互处理程序中,一行输入可能会产生许多行输出,并且,前面途中的程序经常会在读取之前向协作处理进程发送一行数据。这样,当我们想要在发送之前从协作处理进程中读取一些数据的时候,就不能正常地工作了。
这里,有一些方法可以从一个脚本来启动交互处理程序。我们可以添加一个命令语言,以及给pty程序添加一个解释器,但是这样的话那个语言本身可能就比pty程序复杂多了。另外一个方法就是,采用一个命令行语言,然后使用pty_fork函数来发起交互程序。这也是expect程序所做的事情。
我们会采用不同的路径,并且添加一个-d选项,允许pty程序连接到驱动的进程的输入和输出。进程的标准输出是pty的标准输入,反之同样。这和协作处理进程类似,但是在pty的"另外一端",结构和"将伪终端作为协作处理进程的输入输出来运行写作处理进程(扩展后)"是几乎一样的。但是在当前的情况中,pty对驱动的进程做了一步fork和exec的操作。并且,不是使用两个半双工的管道,我们将要在pty和驱动的进程之间,使用一个全双工的管道。
下面的代码展示了do_driver函数的源代码,这个函数会在指定-d选项的时候,被pty的main函数调用(参见前面"pty程序的main函数")。
网友评论