I'd like to be able to inject an initial command into the launching of an interactive process, so that I can do something like this:
我希望能够在启动交互式进程时注入初始命令,以便我可以执行以下操作:
echo "initial command" | INSERT_MAGIC_HERE some_tool
tool> initial command
[result of initial command]
tool> [now I type an interactive command]
What doesn't work:
什么行不通:
-
Just piping the initial command in doesn't work, as this results in stdin not being connected to the terminal
只是管道输入初始命令不起作用,因为这导致stdin没有连接到终端
-
Writing to /dev/pts/[number] sends the output to the terminal, not input to the process as if it were from the terminal
写入/ dev / pts / [number]将输出发送到终端,而不是输入到进程,就像它来自终端一样
What would but with disadvantages:
什么会有缺点:
-
Make a command which forks a child, writes to its stdin and then forwards everything from its own stdin. Downside - terminal control things (like line vs character mode) won't work. Maybe I could do something with proxying of pseudo terminals?
创建一个分叉子命令,写入stdin,然后转发自己的stdin。下行 - 终端控制事物(如线与字符模式)不起作用。也许我可以用代理伪终端做点什么?
-
Make a modified version of xterm (I'm launching one for this task anyway) with a command line option to inject additional commands after encountering a desired prompt string. Ugly.
使用命令行选项创建xterm的修改版本(我正在为此任务启动一个),以在遇到所需的提示字符串后注入其他命令。丑陋。
-
Make a modified version of the tool I'm trying to run so that it accepts an initial command on the command line. Breaks the standard installation.
制作我正在尝试运行的工具的修改版本,以便它在命令行上接受初始命令。打破标准安装。
(The tool of current interest, incidentally, is android's adb shell - I want to open an interactive shell on the phone, run a command automatically, and then have an interactive session)
(顺便提一句,当前感兴趣的工具是android的adb shell - 我想在手机上打开一个交互式shell,自动运行命令,然后进行交互式会话)
3 个解决方案
#1
33
You don't need to write a new tool to forward stdin
- one has already been written (cat
):
你不需要编写一个新的工具来转发stdin - 一个已经写好了(cat):
(echo "initial command" && cat) | some_tool
This does have the downside of connecting a pipe to some_tool
, not a terminal.
这确实有将管道连接到some_tool而不是终端的缺点。
#2
3
The accepted answer is simple and mostly good.
接受的答案很简单,而且大多数都很好。
But it has a disadvantage: the programs gets a pipe as its input, not a terminal. This means that autocompletion will not work. In a lot of cases, this also disables pretty output, and I've heard some programs just refuse to work if stdin is not a terminal.
但它有一个缺点:程序将管道作为输入,而不是终端。这意味着自动完成功能不起作用。在很多情况下,这也会禁用漂亮的输出,我听说如果stdin不是终端,一些程序就会拒绝工作。
The following program solves the problem. It creates a pseudoterminal, spawns a program connected to this pseudoterminal. It first feeds extra input passed via commandline, and then feeds it input given by user via stdin.
以下程序解决了这个问题。它创建一个伪终端,产生一个连接到这个伪终端的程序。它首先提供通过命令行传递的额外输入,然后通过stdin提供用户给出的输入。
For example, ptypipe "import this" python3
makes Python execute "import this" first, and then it drops you to interactive command prompt, with working completion and other stuff.
例如,ptypipe“导入此”python3使Python首先执行“import this”,然后它将您带到交互式命令提示符,工作完成和其他东西。
Likewise, ptypipe "date" bash
runs Bash, which executes date
and then gives a shell to you. Again, with working completion, colourized prompt and so on.
同样,ptypipe“date”bash运行Bash,它执行日期然后给你一个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'\n':
extra_input += b'\n'
# replace LF with CR (shells like CR for some reason)
extra_input = extra_input.replace(b'\n', b'\r')
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()
(Note: I'm afraid this solution contains a possible deadlock. You may want to feed extra_input in small chunks to avoid it)
(注意:我担心这个解决方案可能存在死锁。你可能想要以小块的形式提供extra_input来避免它)
#3
-1
Maybe you could use a here document to pass your input to abd
. E. g. like this (using bc
to do a simple calculation as example).
也许您可以使用here文档将输入传递给abd。 E. g。像这样(使用bc做一个简单的计算作为例子)。
[axe@gromp ~]$ bc <<END
> 3 + 4
> END
7
The bc
session remains open afterwards, so what is provided between the start and end markers (between "<<END" and "END") will be passed to the command.
之后bc会话保持打开状态,因此在开始和结束标记之间(“<< END”和“END”之间)提供的内容将传递给命令。
#1
33
You don't need to write a new tool to forward stdin
- one has already been written (cat
):
你不需要编写一个新的工具来转发stdin - 一个已经写好了(cat):
(echo "initial command" && cat) | some_tool
This does have the downside of connecting a pipe to some_tool
, not a terminal.
这确实有将管道连接到some_tool而不是终端的缺点。
#2
3
The accepted answer is simple and mostly good.
接受的答案很简单,而且大多数都很好。
But it has a disadvantage: the programs gets a pipe as its input, not a terminal. This means that autocompletion will not work. In a lot of cases, this also disables pretty output, and I've heard some programs just refuse to work if stdin is not a terminal.
但它有一个缺点:程序将管道作为输入,而不是终端。这意味着自动完成功能不起作用。在很多情况下,这也会禁用漂亮的输出,我听说如果stdin不是终端,一些程序就会拒绝工作。
The following program solves the problem. It creates a pseudoterminal, spawns a program connected to this pseudoterminal. It first feeds extra input passed via commandline, and then feeds it input given by user via stdin.
以下程序解决了这个问题。它创建一个伪终端,产生一个连接到这个伪终端的程序。它首先提供通过命令行传递的额外输入,然后通过stdin提供用户给出的输入。
For example, ptypipe "import this" python3
makes Python execute "import this" first, and then it drops you to interactive command prompt, with working completion and other stuff.
例如,ptypipe“导入此”python3使Python首先执行“import this”,然后它将您带到交互式命令提示符,工作完成和其他东西。
Likewise, ptypipe "date" bash
runs Bash, which executes date
and then gives a shell to you. Again, with working completion, colourized prompt and so on.
同样,ptypipe“date”bash运行Bash,它执行日期然后给你一个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'\n':
extra_input += b'\n'
# replace LF with CR (shells like CR for some reason)
extra_input = extra_input.replace(b'\n', b'\r')
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()
(Note: I'm afraid this solution contains a possible deadlock. You may want to feed extra_input in small chunks to avoid it)
(注意:我担心这个解决方案可能存在死锁。你可能想要以小块的形式提供extra_input来避免它)
#3
-1
Maybe you could use a here document to pass your input to abd
. E. g. like this (using bc
to do a simple calculation as example).
也许您可以使用here文档将输入传递给abd。 E. g。像这样(使用bc做一个简单的计算作为例子)。
[axe@gromp ~]$ bc <<END
> 3 + 4
> END
7
The bc
session remains open afterwards, so what is provided between the start and end markers (between "<<END" and "END") will be passed to the command.
之后bc会话保持打开状态,因此在开始和结束标记之间(“<< END”和“END”之间)提供的内容将传递给命令。