【问题标题】:How can I wrap a command with a line-buffered prompt?如何使用行缓冲提示包装命令?
【发布时间】:2016-03-01 23:00:07
【问题描述】:

我正在寻找一个命令,它首先通过给定的 command 生成一个进程,然后使用给定的 prompt string 提示用户输入一行(使用 readline功能),将输入的行通过管道传输到该进程中,然后重复。进程的任何输出都打印在提示行上方的行上,以防止混乱,使提示始终是屏幕上的最后一行,但进程可以随时输出一些东西。

例如,一个提示命令,如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 客户端命令,它从标准输入读取命令并将简单的消息输出到标准输出。它没有界面,甚至没有提示,它只是直接从标准输入和标准输出读取和打印:

$ 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 的消息破坏,而是将消息打印在提示的 above 行上。上面提到的 rlwrap 程序正确地做到了这一点。我能说的唯一缺少的是提示字符串。

【问题讨论】:

    标签: bash prompt readline read-eval-print-loop


    【解决方案1】:

    在您的脚本中,您可以定义以下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
    

    【讨论】:

    • 真是太好了!但据我了解,它为输入的每一行创建了一个新流程。我希望它事先生成进程并将输入每次都通过管道传输到同一个进程中,而不是每次都创建一个新进程。而且我还需要它能够在任何给定时间读取和打印进程的输出,而不仅仅是在输入一行之后,它应该能够在不破坏输入提示或当前输入的任何内容的情况下处理它输入之前的行。我将添加另一个示例。
    【解决方案2】:

    首先,我对 rlwrap 的看法是错误的,你可以使用提示符:

    rlwrap -S "> " grep hi
    

    而且效果很好。但是,如果您在进程打印某些内容时输入了某些内容,则会留下提示的伪影。

    然后我找到了 socat,它可以做与上面基本相同的事情(除其他外),但它不会留下那些工件(通过在您键入时阻止 stdout 直到您按下输入,线路再次清晰):

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-05
      • 2018-02-13
      • 2016-07-04
      • 2013-03-28
      • 1970-01-01
      • 2014-01-02
      • 2020-09-24
      • 1970-01-01
      相关资源
      最近更新 更多