我如何将初始输入传送到可交互的进程中?
- 2024-10-17 08:47:00
- admin 原创
- 92
问题描述:
我希望能够在启动交互式进程时注入一个初始命令,这样我就可以执行如下操作:
echo "initial command" | INSERT_MAGIC_HERE some_tool
tool> initial command
[result of initial command]
tool> [now I type an interactive command]
无效的方法:
仅通过管道传输初始命令不起作用,因为这会导致 stdin 无法连接到终端
写入 /dev/pts/[number] 会将输出发送到终端,而不是像来自终端一样输入到进程
但有缺点的是:
创建一个命令,该命令分叉一个子进程,写入其标准输入,然后从其自己的标准输入转发所有内容。缺点 - 终端控制(如行模式与字符模式)不起作用。也许我可以用伪终端代理做些什么?
制作一个 xterm 的修改版本(无论如何我都会为这个任务启动一个),并在遇到所需的提示字符串后使用命令行选项注入其他命令。太丑了。
制作我正在尝试运行的工具的修改版本,以便它接受命令行上的初始命令。破坏标准安装。
(顺便说一下,目前感兴趣的工具是 android 的 adb shell - 我想在手机上打开一个交互式 shell,自动运行一个命令,然后进行交互式会话)
解决方案 1:
您不需要编写新工具来转发stdin
- 已经编写了一个(cat
):
(echo "initial command" && cat) | some_tool
这确实存在将管道连接到而some_tool
不是终端的缺点。
解决方案 2:
接受的答案很简单,而且大多是好的。
但它有一个缺点:程序将管道作为输入,而不是终端。这意味着自动完成将不起作用。在很多情况下,这还会禁用漂亮的输出,而且我听说如果 stdin 不是终端,某些程序就会拒绝工作。
以下程序解决了这个问题。它创建一个伪终端,生成一个连接到该伪终端的程序。它首先提供通过命令行传递的额外输入,然后提供用户通过 stdin 提供的输入。
例如,ptypipe "import this" python3
让 Python 首先执行“import this”,然后将您带到交互式命令提示符,其中包含工作完成和其他内容。
同样,ptypipe "date" bash
运行 Bash,它会执行date
并返回一个 shell。同样,具有工作完成、彩色提示符等。
#!/usr/bin/env python3
import sys
import os
import pty
import tty
import select
import subprocess
STDIN_FILENO = 0
STDOUT_FILENO = 1
STDERR_FILENO = 2
def _writen(fd, data):
while data:
n = os.write(fd, data)
data = data[n:]
def main_loop(master_fd, extra_input):
fds = [master_fd, STDIN_FILENO]
_writen(master_fd, extra_input)
while True:
rfds, _, _ = select.select(fds, [], [])
if master_fd in rfds:
data = os.read(master_fd, 1024)
if not data:
fds.remove(master_fd)
else:
os.write(STDOUT_FILENO, data)
if STDIN_FILENO in rfds:
data = os.read(STDIN_FILENO, 1024)
if not data:
fds.remove(STDIN_FILENO)
else:
_writen(master_fd, data)
def main():
extra_input = sys.argv[1]
interactive_command = sys.argv[2]
if hasattr(os, "fsencode"):
# convert them back to bytes
# http://bugs.python.org/issue8776
interactive_command = os.fsencode(interactive_command)
extra_input = os.fsencode(extra_input)
# add implicit newline
if extra_input and extra_input[-1] != b'
':
extra_input += b'
'
# replace LF with CR (shells like CR for some reason)
extra_input = extra_input.replace(b'
', b'
')
pid, master_fd = pty.fork()
if pid == 0:
os.execlp("sh", "/bin/sh", "-c", interactive_command)
try:
mode = tty.tcgetattr(STDIN_FILENO)
tty.setraw(STDIN_FILENO)
restore = True
except tty.error: # This is the same as termios.error
restore = False
try:
main_loop(master_fd, extra_input)
except OSError:
if restore:
tty.tcsetattr(0, tty.TCSAFLUSH, mode)
os.close(master_fd)
return os.waitpid(pid, 0)[1]
if __name__ == "__main__":
main()
(注意:我担心这个解决方案可能存在死锁。您可能需要以小块的形式提供 extra_input 以避免这种情况)
解决方案 3:
使用“expect”程序可以轻松实现这一点,该程序旨在让您编写脚本来与程序进行交互。
我通过编写一个预期脚本 bc.exp 来测试这一点,以启动计算器“bc”并向其发送命令“obase=16”以使其进入十六进制输出模式,然后将控制权交给我。
该脚本(在名为 bc.exp 的文件中)是
spawn bc
send "obase=16
"
interact {
exit
}
一个人运行
expect bc.exp
解决方案 4:
@caf 的回答很可靠。
我想我会在这里用一个对我来说很有效的相关选项来扩展它。
我的<initial command>
实际上是命令列表,位于文本文件中。因此,我想将其导入<some command>
并连接到stdin
。
cat
处理这个问题很好,它接受-
读取stdin
:
cat init_file - | some_command
这与 cafs 答案中讨论的有相同的限制。
解决方案 5:
您还可以(在某些情况下,从终端运行时)使用tee
它直接写入终端输出。名称tee
指的是像 T 一样,将 stdin 传递到 stdout,同时写入文件(/dev/tty,在本例中为终端)。
echo "initial command" | tee /dev/tty | some_tool
https://unix.stackexchange.com/a/178754/89546
解决方案 6:
如果您在 tmux 中运行,您可以告诉它发送按键。
例如
$ stty -echo; tmux send-keys test; stty echo
将输入test
到您的终端输入。(stty 阻止在终端上看到按键)
或者如果你使用 vi 模式:
stty -echo
tmux send-keys escape 0
stty echo
read -p "rename: " -e -i 'old name' new_name
这会将 escape 和 0 放入输入缓冲区,以将以下 readline 置于命令模式,并将光标移动到 0。
解决方案 7:
也许您可以使用此处的文档将您的输入传递给abd
。例如像这样(使用bc
进行简单计算作为示例)。
[axe@gromp ~]$ bc <<END
> 3 + 4
> END
7
会话bc
随后保持打开状态,因此开始和结束标记之间(“<<END”和“END”之间)提供的内容将传递给命令。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件