linux交互式输入自动化 read & expect

本文介绍如何使用Bash脚本进行交互式输入,并通过Expect实现自动化输入处理。包括Bash读取命令行或文件输入的方法,以及Expect的基本用法和安装指南。

输入脚本:

#!/bin/bash

#file name : interactive.sh

read -p "input number:" num;

read -p "input string:" str

echo num: $num str:$str;


命令行输入:

echo -e "1\nhelloworld\n" | ./interactive.sh


输入内容较多时也可以用单独的文件,从文件中导入交互式输入数据:

echo -e "1\nhelloworld\n" > input.data

./interactive.sh < input.data


用expect实现自动化:

expect默认情况下没有附带于常见的linux发行版,需要安装。

ubuntu14.04下安装:sudo apt-get install expect

#!/usr/bin/expect

#expect.sh

spawn ./interactive.sh

expect "input number"

send "1\n"

expect "intput string"

send "helloworld\n"

expect eof


#运行: ./expect.sh

命令解释:

spawn:参数指定需要自动化哪个命令

expect:参数提供需要等待的i消息

send:要发送的消息

expect eof 指明指令交互结束


注意头命令#!/usr/bin/expect,如果出现安装了expect但是找不到相关命令,请参考:

对于expect的隐式调用,所以需要在第一行声明对于本脚本代码的解释器。

具体解释不再详细论述,请见博文:http://blog.youkuaiyun.com/sanwu2010/article/details/14454237


&lt;think&gt;我们正在处理一个关于在Linux环境下使用Python调用交互式终端的问题。根据用户的问题,我们需要找到一种方法来用Python启动一个交互式终端(如bash),并能够与之进行交互(即发送命令并接收输出)。 参考提供的引用: 引用[1]展示了一个简单的终端模拟,但它使用的是`subprocess.getoutput`,这个函数会等待命令执行完成并返回所有输出,并不适合交互式命令(例如那些需要持续输入的命令,如`python`交互模式或`top`)。 引用[2]提到了`os.system`,它也是阻塞的,并且不适合交互式会话。 引用[3]展示了如何在终端中运行Python脚本,但这是从终端调用Python,而不是从Python调用交互式终端。 引用[4]提到了`exec`系统方法,但指出它不会返回,因为它替换当前进程。 因此,我们需要一个可以持续与子进程交互的方法。在Python中,我们可以使用`subprocess`模块的`Popen`类,它可以启动一个进程并允许我们通过管道(stdin, stdout, stderr)与之交互。 具体步骤: 1. 使用`subprocess.Popen`启动一个交互式终端程序(如`/bin/bash`),并设置标准输入、输出和错误为管道。 2. 然后我们可以通过`proc.stdin`写入命令,通过`proc.stdout`读取输出。 但是,交互式终端通常会对输入输出进行缓冲,并且我们还需要处理终端的控制序列(如果涉及)。另外,我们还需要避免死锁(即当读写同时发生时,缓冲区填满导致进程阻塞)。 一个简单的方法是使用伪终端(pty)来模拟终端环境,因为有些程序(如shell)在非终端环境下会改变行为(比如不显示提示符)。不过,如果只是简单的命令交互,我们可以使用管道,但复杂的交互式程序(如vim, top)可能需要伪终端。 根据用户需求,我们只需要调用交互式终端(比如bash),那么我们可以使用伪终端(pty)模块来创建一个终端。 然而,用户的问题中并没有特别指出需要处理复杂的交互式程序(如那些要求终端特性的程序),所以我们先提供一种使用普通管道的方法,如果遇到问题再考虑使用pty。 但注意:使用普通管道时,bash等shell可能不会提供提示符,并且可能不会像在终端中那样工作。因此,为了真正的交互式终端体验,我们使用伪终端(pty)。 这里我们提供两种方法: 方法1:使用subprocess.Popen和管道(适用于简单交互,但可能不显示提示符或某些行为异常) 方法2:使用pty模块(更接近真实终端) 由于用户明确要求&ldquo;交互式终端&rdquo;,我们推荐使用pty。 示例代码(使用pty): 注意:pty是Linux特有的模块。 我们将使用`pty.openpty()`来创建一对伪终端,然后启动子进程并将其标准输入输出连接到伪终端。 步骤: 1. 创建伪终端主从设备。 2. 使用`subprocess.Popen`启动bash(或其他shell),将其标准输入、输出、错误都连接到从设备。 3. 然后我们可以通过主设备与子进程交互。 但是,我们还需要注意非阻塞读取以避免程序卡住,或者使用线程来处理输入输出。 为了简化,我们可以使用一个循环,从主设备读取输出并打印,同时等待用户输入并写入。 但是,由于用户可能希望在自己的Python程序中嵌入一个终端,我们这里提供一个简单的例子。 注意:这个例子中,我们只是简单地将主设备的输出打印到屏幕,并将用户的输入发送到主设备。但是,由于我们同时需要读取和写入,为了避免阻塞,我们可以使用多线程或者将主设备设置为非阻塞。这里我们使用非阻塞读取,但要注意处理没有数据时的情形。 然而,非阻塞读取可能会使CPU占用过高,因此我们也可以使用select模块来监视主设备是否有数据可读。 下面是一个使用select的例子,它可以在有数据时读取,没有数据时等待: ```python import os import pty import select import subprocess # 创建伪终端 master, slave = pty.openpty() # 使用subprocess.Popen启动bash,并连接到从设备 p = subprocess.Popen( [&quot;/bin/bash&quot;, &quot;-i&quot;], # -i 表示交互式shell stdin=slave, stdout=slave, stderr=slave, close_fds=True ) # 关闭从设备,因为子进程已经使用它了 os.close(slave) # 设置主设备为非阻塞(这样在读取时如果没有数据就会立即返回,而不是阻塞) # 但是select可以处理阻塞,所以我们可以不设置非阻塞,而是用select来等待 # 这里我们使用阻塞模式,但通过select来轮询 # 循环:从标准输入读取(用户输入)并写入主设备,从主设备读取(程序输出)并打印 try: while p.poll() is None: # 当子进程还在运行 # 使用select监视主设备和标准输入(sys.stdin) # 注意:标准输入是0 rlist, _, _ = select.select([master, 0], [], []) for r in rlist: if r == master: # 主设备有数据可读 data = os.read(master, 1024) if data: # 将数据输出到标准输出 os.write(1, data) else: # 读取到0字节,表示EOF break elif r == 0: # 标准输入有数据 data = os.read(0, 1024) if data: os.write(master, data) else: break except OSError: pass finally: # 关闭主设备 os.close(master) # 等待子进程结束 p.wait() ``` 但是,上面的代码中,标准输入(0)是文件描述符,通常对应sys.stdin。但是,在非阻塞模式下,读取标准输入可能会遇到问题。另外,我们直接使用文件描述符读写,没有经过解码,所以输出的是字节。 如果用户希望使用字符串,则需要进行编码和解码。但是,终端交互通常是字节流。 另外,上述代码在用户输入时,终端回显是bash自己处理的,所以我们会看到两次输入(一次是用户输入时系统回显,一次是bash回显),这会导致重复。为了避免这种情况,我们可以让bash不处理回显,或者禁用终端的回显。但这样比较复杂。 实际上,在伪终端中,我们通常希望像普通终端一样工作,所以回显应该由从设备(bash)来处理。但是,主设备(我们的程序)也会收到回显的字符,所以当我们从主设备读取时,会包括我们输入的字符(因为bash会回显)。这实际上是正常的,因为我们在终端中输入时,会先看到自己的输入(由终端显示),然后命令的输出。 但是,在上面的代码中,我们同时从标准输入读取并写入主设备,并且从主设备读取并写入标准输出(1)。这样,当我们输入一个字符时,它会先被标准输入读取,然后写入主设备,然后bash会回显这个字符(通过主设备读取并打印到标准输出),所以我们在屏幕上看到两次?实际上,第一次是系统标准输入的回显(通常由终端驱动处理),第二次是bash的回显。 为了避免这种情况,我们需要禁用终端驱动对标准输入的回显。在Unix中,我们可以使用termios来控制标准输入的模式。 由于这变得复杂,我们考虑另一种方式:使用`pexpect`模块,它封装了这些细节,提供了更简单的接口。但是用户要求使用Python标准库,所以我们不使用第三方库。 因此,我们调整:在从标准输入读取时,禁用本地回显,这样我们只看到bash回显的字符。 如何禁用标准输入的回显?我们可以设置标准输入的属性: ```python import termios import sys # 保存原来的设置 old_attr = termios.tcgetattr(0) new_attr = old_attr[:] new_attr[3] = new_attr[3] &amp; ~termios.ECHO # 禁用回显 try: termios.tcsetattr(0, termios.TCSANOW, new_attr) except termios.error: pass ``` 但是,在退出时我们需要恢复设置。 另外,我们还需要设置标准输入为原始模式(raw)吗?实际上,我们只需要禁用回显。但是,在终端中,我们通常需要处理控制字符(如Ctrl+C)等,所以最好设置为原始模式,这样我们可以将控制字符传递给子进程。不过,这会使我们的程序更复杂。 考虑到复杂性,如果用户只是需要执行一些命令,而不需要完全模拟终端(比如不需要运行全屏程序如vim),那么使用管道可能更简单。但用户要求的是交互式终端,所以我们还是使用pty。 因此,我们修改代码,在开始时设置标准输入为不回显,并在结束时恢复。 由于代码较长,我们给出一个简化版本,并加上必要的注释。 但是,注意:在Python中,我们可以使用`tty`模块来设置标准输入为原始模式,这样可以更好地处理控制字符。 这里我们使用`tty.setraw`来设置标准输入为原始模式,这样按键(如Ctrl+C)会作为普通字符传递给子进程,由子进程来处理。 完整代码: ```python import os import pty import select import subprocess import sys import termios import tty # 保存标准输入的原始属性 old_attr = termios.tcgetattr(sys.stdin.fileno()) try: # 设置标准输入为原始模式 tty.setraw(sys.stdin.fileno()) # 创建伪终端 master, slave = pty.openpty() # 启动bash p = subprocess.Popen( [&quot;/bin/bash&quot;, &quot;-i&quot;], stdin=slave, stdout=slave, stderr=slave, close_fds=True ) os.close(slave) # 循环 try: while p.poll() is None: rlist, _, _ = select.select([master, sys.stdin.fileno()], [], []) for r in rlist: if r == master: data = os.read(master, 1024) if not data: break os.write(sys.stdout.fileno(), data) else: # 标准输入有数据 data = os.read(sys.stdin.fileno(), 1024) if not data: break os.write(master, data) except OSError: pass finally: os.close(master) p.wait() finally: # 恢复标准输入的原始设置 termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_attr) ``` 注意:这个代码在用户按下Ctrl+D(EOF)时,会退出bash,然后循环结束。而Ctrl+C会被传递给bash,由bash处理(即中断当前命令)。 但是,这个代码在Windows上不能运行,因为pty是Unix特有的。 另外,用户可能希望在自己的程序中嵌入一个终端,这个代码提供了一个基本的框架。 但是,由于用户问题中要求的是&ldquo;调用交互式终端&rdquo;,我们也可以使用更简单的方法:直接使用`subprocess.Popen`并传递`sys.stdin`和`sys.stdout`,但这样可能无法满足某些交互式程序的需求(因为它们需要终端设备)。不过,我们可以设置`shell=True`,并且使用`stdin=subprocess.PIPE, stdout=subprocess.PIPE`,但这仍然不是真正的终端。 如果用户不需要终端特性,可以使用以下简单方法: ```python import subprocess p = subprocess.Popen([&quot;/bin/bash&quot;, &quot;-i&quot;], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # 然后通过p.stdin.write和p.stdout.read交互,但这样容易死锁,而且没有终端特性。 ``` 因此,我们推荐使用伪终端的方法。 总结:在Linux环境下,使用Python调用交互式终端,我们可以使用`pty`模块来创建伪终端,然后启动bash进程,并通过设置标准输入为原始模式来与bash交互。 注意:上面的代码只是一个示例,实际应用中可能需要处理更多细节(如信号、窗口大小变化等)。 根据用户的需求,我们提供了使用pty的解决方案。 相关问题: 1. 如何在Python中捕获交互式命令的输出? 2. 如何在不使用伪终端的情况下与子进程交互? 3. 在Python中如何实现一个简单的终端模拟器? 但是,根据用户的问题,我们只生成了调用交互式终端的方法,所以相关问题可以围绕这个主题。 因此,我们生成以下相关问题: 1. 如何用Python实现一个简单的终端模拟器? 2. 在Python中调用交互式命令时,如何处理控制字符(如Ctrl+C)? 3. 如何将伪终端的输出重定向到图形用户界面(GUI)? 注意:用户的问题中引用了几个引用,但我们的解决方案主要基于标准库的`pty`和`subprocess`,没有直接使用这些引用中的方法(如os.system或subprocess.getoutput),因为它们不适合交互式终端。 因此,我们在回答中不需要引用这些引用,因为我们的解决方案与它们不同。 但是,如果用户问的是如何执行单个命令而不是持续交互,那么引用中的方法就适用。而用户明确要求&ldquo;交互式终端&rdquo;,所以我们的解决方案是合适的。 最后,我们给出代码示例,并解释其工作原理。 由于代码较长,我们将其放在代码块中,并加上必要的注释。 注意:用户要求使用中文回答。 因此,我们给出如下回答:&lt;/think&gt;在 Linux 环境下使用 Python 调用交互式终端,可以通过伪终端(pseudoterminal, pty)实现。这是最接近真实终端交互的方式,能正确处理控制序列、信号和交互式命令(如 `vim`、`top` 等)。以下是完整解决方案: ### 方法 1:使用 `pty` 模块(推荐) ```python import os import pty import select import subprocess import termios import tty def run_interactive_terminal(): # 创建伪终端主从设备 master_fd, slave_fd = pty.openpty() # 启动 Bash 子进程并连接到从设备 p = subprocess.Popen( [&quot;/bin/bash&quot;, &quot;-i&quot;], # -i 启动交互式 Shell stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, start_new_session=True ) os.close(slave_fd) # 关闭从设备,子进程已接管 # 保存原始终端设置 old_settings = termios.tcgetattr(sys.stdin.fileno()) try: # 设置标准输入为原始模式(直接传递控制字符) tty.setraw(sys.stdin.fileno()) while True: # 监视主设备和标准输入 rlist, _, _ = select.select([master_fd, sys.stdin], [], []) if sys.stdin in rlist: # 用户输入 data = os.read(sys.stdin.fileno(), 1024) if data == b&#39;\x04&#39;: # Ctrl+D 退出 break os.write(master_fd, data) # 转发到子进程 if master_fd in rlist: # 子进程输出 data = os.read(master_fd, 1024) if not data: # EOF break os.write(sys.stdout.fileno(), data) # 输出到屏幕 finally: # 恢复终端设置并关闭资源 termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_settings) os.close(master_fd) p.wait() if __name__ == &quot;__main__&quot;: import sys run_interactive_terminal() ``` ### 关键机制说明 1. **伪终端(pty)**: - `pty.openpty()` 创建虚拟终端主从设备 - 子进程(如 `bash`)连接到从设备(`slave_fd`) - Python 通过主设备(`master_fd`)与子进程交互 2. **原始模式(Raw Mode)**: - `tty.setraw()` 禁用本地回显和缓冲 - 控制字符(如 `Ctrl+C`)直接传递给子进程处理 3. **双向通信**: - 使用 `select` 同时监听用户输入和子进程输出 - 数据通过 `os.read()`/`os.write()` 实时转发 ### 方法 2:使用 `pexpect` 库(简化版) 若允许第三方库,`pexpect` 更简洁: ```bash pip install pexpect ``` ```python import pexpect child = pexpect.spawn(&quot;/bin/bash&quot;, encoding=&quot;utf-8&quot;) child.interact() # 将控制权交给用户 ``` ### 应用场景示例 1. **自动化交互式工具**: ```python child = pexpect.spawn(&quot;ftp localhost&quot;) child.expect(&quot;Name&quot;) child.sendline(&quot;user&quot;) child.expect(&quot;Password&quot;) child.sendline(&quot;pass&quot;) ``` 2. **嵌入式终端模拟器**: ```python # 在 PyQt 中嵌入终端 from PyQt5 import QtWidgets from PyQt5.QtCore import QProcess class TerminalWidget(QtWidgets.QWidget): def __init__(self): super().__init__() self.process = QProcess() self.process.start(&quot;bash&quot;, [&quot;-i&quot;]) # 将进程输入输出连接到 GUI 组件... ``` ### 常见问题解决 - **输出卡顿**:确保每次读取后刷新缓冲区 `sys.stdout.flush()` - **终端尺寸异常**: ```python import fcntl import struct import termios # 设置终端尺寸(行数x列数) winsize = struct.pack(&quot;HHHH&quot;, 40, 120, 0, 0) fcntl.ioctl(master_fd, termios.TIOCSWINSZ, winsize) ``` - **信号传递**:通过 `start_new_session=True` 确保信号正确传递到子进程 &gt; 注意:伪终端方案仅适用于 Unix/Linux 系统,Windows 需使用 `winpty` 等替代方案[^1][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值