如何在一个带有行缓冲提示符的命令中包装(wrap)其它命令?

3
我正在寻找一个命令,首先通过给定的命令生成一个进程,然后使用给定的提示字符串提示用户输入一行(使用readline功能),将输入的行管道传输到该进程中,并重复此过程。任何进程的输出都会打印在提示行之前的行上,以防止混乱,因此提示始终是屏幕上的最后一行,但进程可以随时输出内容。
例如,prompt命令如下:prompt -p "> " cat在每行输入之前运行cat,并显示提示。它应该看起来像这样:
$ prompt -p "> " cat
> hello
hello
> every time it's time for me to type, there's a prompt!
every time it's time for me to type, there's a prompt!
> for sure
for sure

也许您还可以像这样为命令的输出指定提示符:
$ prompt -p "[IN] " -o "[OUT] " grep hi
[IN] hello
[IN] this is another example
[OUT] this is another example
[IN] it sure is, i'm glad you know

我找到了 rlwrap(https://github.com/hanslub42/rlwrap),它似乎可以使用 readline 功能进行行缓冲,但没有输入提示符。
基本上,我想要一个命令,可以将任何操作输入流的命令转换为友好的 repl。
这个方法几乎可行,但每当进程输出内容时,光标位置都会出现错误:
CMD="grep hi" # as an example

prompt () {
    while true
    do
        printf "> \033\067"
        read -e line || break
        echo $line > $1
    done
}

prompt >(stdbuf -oL $CMD |
    stdbuf -oL sed 's/^/< /' |
    stdbuf -oL sed 's/^/'`echo -ne "\033[0;$(expr $(tput lines) - 1)r\033[$(expr $(tput lines) - 1);0H\033E"`'/;s/$/'`echo -ne "\033[0;$(tput lines)r\033\070\033M"`'/')

这是为了更加清晰而提供的另一个例子。想象一下一个简单的IRC客户端命令,它从stdin读取命令,并将简单的消息输出到stdout。它没有任何界面甚至提示,它只是直接从stdin读取并直接打印到stdout。
$ irc someserver
NOTICE (*): *** Looking up your hostname...
NOTICE (*): *** Found your hostname
001 (madeline): Welcome to someserver IRC!! madeline!madeline@somewhere
(...)
/join #box
JOIN (): #box
353 (madeline = #box): madeline @framboos
366 (madeline #box): End of /NAMES list.
hello!
<madeline> hello!
(5 seconds later)
<framboos> hii

使用提示命令,它看起来会更像这样:
$ prompt -p "[IN] " -o "[OUT] " irc someserver
[OUT] NOTICE (*): *** Looking up your hostname...
[OUT] NOTICE (*): *** Found your hostname
[OUT] 001 (madeline): Welcome to someserver IRC!! madeline!madeline@somewhere
(...)
[IN] /join #box
[OUT] JOIN (): #box
[OUT] 353 (madeline = #box): madeline @framboos
[OUT] 366 (madeline #box): End of /NAMES list.
[IN] hello!
[OUT] <madeline> hello!
(5 seconds later)
[OUT] <framboos> hii
[IN] 

重点是只生成一个进程,每输入一行都会传输到同一个进程中,而不是为每一行生成新的进程。还要注意 [IN] 提示符没有被 framboos 的消息覆盖,而是在提示符的 上方 打印消息。上面提到的 rlwrap 程序可以正确地实现这一点。据我所知,它唯一缺少的是提示字符串 (s)。
2个回答

3

首先,我关于rlwrap的错误,你可以在其中使用提示符:

rlwrap -S "> " grep hi

它的效果相当不错。但是,如果您在进程输出某些内容时有输入内容,则会留下提示的痕迹。

然后我发现了socat,它可以基本上像上面一样做同样的事情(以及其他事情),但它不会留下这些痕迹(通过阻塞标准输出,直到您按回车键并且该行再次清除):

socat READLINE,prompt="[IN] " EXEC:"stdbuf -oL grep hi"
[IN] hello
[IN] this is another example
this is another example
[IN] it sure is, i'm glad you know

然后我可以使用sed将提示符添加到输出中:
socat READLINE,prompt="[IN] " SYSTEM:"stdbuf -oL grep hi | stdbuf -oL sed \'s/^/[OUT] /\'"
[IN] hello
[IN] this is another example
[OUT] this is another example
[IN] it sure is, i'm glad you know

0
在你的脚本中,你可以定义以下的prompt例程:
#!/bin/bash

prompt() {
    if [[ "$3" = "-o" ]]; then
        symbol1="$2"
        symbol2="$4"
        shift 4
        process="$*"
        while true; do
            printf "%s" "$symbol1"
            read -e line
            output=$( echo "$line" | $process )
            if [[ "$output" != "" ]]; then
                printf "%s" "$symbol2"
                echo "$output"
            fi
        done
    else
        symbol="$2"
        shift 2
        process="$*"
        while true; do
            printf "%s" "$symbol"
            read -e line
            echo "$line" | $process
        done
     fi
}

然后您可以将脚本文件作为源文件,以便使例程可用:source ./file


使用示例:

测试1

$ prompt -p "> " cat
> hello
hello
> every time it's time for me to type, there's a prompt!
every time it's time for me to type, there's a prompt!
> for sure
for sure 

测试2

$ prompt -p "[IN] " -o "[OUT] " grep hi
[IN] hello
[IN] this is another example
[OUT] this is another example
[IN] it sure is, i'm glad you know

这真的很好!但据我理解,它为每输入一行创建一个新进程。我希望它能提前生成进程,并将输入管道传输到同一进程中,而不是每次都创建一个新进程。我还需要它能够随时读取和打印进程的输出,而不仅仅是在输入一行后立即进行,而且它应该能够处理它,而不会覆盖输入提示或任何当前输入的内容。我会再举个例子。 - Aardbei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接