【问题标题】:How to enable the up/down arrow keys to show previous inputs when using `read`?使用`read`时如何启用向上/向下箭头键以显示以前的输入?
【发布时间】:2011-07-22 10:36:24
【问题描述】:

我正在无限循环中等待用户输入(使用“读取”),并希望拥有命令历史记录,即能够使用向上和向下箭头键显示之前已经输入的输入获得 ^[[A 和 ^[[B。这可能吗?


感谢@l0b0 的回答。它让我朝着正确的方向前进。在玩了一段时间后,我意识到我还需要以下两个功能,但我还没有设法获得它们:

  • 如果我按下并在上一个命令中添加一些内容,我希望将整个内容保存在历史记录中,而不仅仅是添加内容。示例

    $ ./up_and_down
    输入命令:你好
    ENTER
    输入命令:
    向上
    输入命令:你好
    ENTER
    输入命令:
    向上
    输入命令:你
    (而不是“你好”)

  • 如果因为我在历史数组的末尾而无法继续上升,我不希望光标移动到上一行,而是希望它保持固定。

这是我目前所拥有的(up_and_down):

#!/usr/bin/env bash
set -o nounset -o errexit -o pipefail

read_history() {
    local char
    local string
    local esc=$'\e'
    local up=$'\e[A'
    local down=$'\e[B'
    local clear_line=$'\r\e[K'


    local history=()
    local -i history_index=0

    # Read one character at a time
    while IFS="" read -p "Enter command:" -n1 -s char ; do
        if [[ "$char" == "$esc" ]]; then 
            # Get the rest of the escape sequence (3 characters total)
            while read -n2 -s rest ; do
                char+="$rest"
                break
            done
        fi

        if [[ "$char" == "$up" && $history_index > 0 ]] ; then
            history_index+=-1
            echo -ne $clear_line${history[$history_index]}
        elif [[ "$char" == "$down" && $history_index < $((${#history[@]} - 1)) ]] ; then
            history_index+=1
            echo -ne $clear_line${history[$history_index]}
        elif [[ -z "$char" ]]; then # user pressed ENTER
            echo
            history+=( "$string" )
            string=
            history_index=${#history[@]}
        else
            echo -n "$char"
            string+="$char"
        fi
    done
}
read_history

【问题讨论】:

    标签: bash shell


    【解决方案1】:

    -e 选项与read 命令结合使用内置history 命令的两种解决方案:

    # version 1
    while IFS="" read -r -e -d $'\n' -p 'input> ' line; do 
       echo "$line"
       history -s "$line"
    done
    
    # version 2
    while IFS="" read -r -e -d $'\n' -p 'input> ' line; do 
       echo "$line"
       echo "$line" >> ~/.bash_history
       history -n
    done
    

    【讨论】:

    • 非常感谢!找了很久,效果很好!不过,我只需要 -e -p 参数。剩下的(设置 IFS 为空、原始输入、设置 \n delim)是为了什么?
    • 很好的信息。几乎所有情况都应使用版本 1。在版本 2 中,您输入的垃圾最终会出现在 bash 历史记录中,在那里它可能会被 bash 意外召回和不恰当地执行。您也可能悄悄地将私人或安全信息泄露到历史记录中。
    • 一个关于如何做到这一点的网络搜索......哇,第 1 版很好用。这个页面只是为我节省了很多时间。谢谢。
    【解决方案2】:

    有趣的问题 - 这是目前的结果:

    #!/usr/bin/env bash
    set -o errexit -o nounset -o pipefail
    read_history() {
        local char=
        local string=
        local -a history=( )
        local -i histindex=0
    
        # Read one character at a time
        while IFS= read -r -n 1 -s char
        do
            if [ "$char" == $'\x1b' ] # \x1b is the start of an escape sequence
            then
                # Get the rest of the escape sequence (3 characters total)
                while IFS= read -r -n 2 -s rest
                do
                    char+="$rest"
                    break
                done
            fi
    
            if [ "$char" == $'\x1b[A' ]
            then
                # Up
                if [ $histindex -gt 0 ]
                then
                    histindex+=-1
                    echo -ne "\r\033[K${history[$histindex]}"
                fi
            elif [ "$char" == $'\x1b[B' ]
            then
                # Down
                if [ $histindex -lt $((${#history[@]} - 1)) ]
                then
                    histindex+=1
                    echo -ne "\r\033[K${history[$histindex]}"
                fi
            elif [ -z "$char" ]
            then
                # Newline
                echo
                history+=( "$string" )
                string=
                histindex=${#history[@]}
            else
                echo -n "$char"
                string+="$char"
            fi
        done
    }
    read_history
    

    【讨论】:

    • 这就是我想要的。你能解释一下你的答案吗?具体来说:您是否将输入读取为两个单独的字符?为什么? \x1b 转义码是什么?为什么要在前面加上$?你是设置IFS还是清空? local char=;local char; 有区别吗?您能解释一下read -r -n 1 -s char 中的选项吗?如何阅读local 中的选项(如-a-i)?
    • 在代码中添加了一些 cmets。 IFS= 将其设置为空字符串以避免word splittinglocal char 只是声明了一个函数局部变量,但之后没有等号,它是未定义的(此时它是空字符串)。至于 $'read/local 选项,请参见 Bash 手册页。
    • 谢谢。知道了。 \x1b 是转义键的十六进制代码,箭头键被编码为转义键 (\x1b)、左括号 (\x5b 或 '[') 和四个大写字母之一 (ABCD),具体取决于箭头.构造 $'...' 用于显示转义字符。我仍然不确定 K 和 \r\033(回车和转义?)在做什么
    • \r 表示返回到行首(回车)。 K 转义码是将(Emacs-sp33k 中的“kill”)剪切到行尾。
    • Micha 使用 read -e 的答案要简单得多,可能应该是官方的答案。
    【解决方案3】:

    我使用rlwrap 在不支持它的程序中启用 readline 功能。也许你可以试试这个。 rlwrap 代表 readline 包装器。此命令拦截您的向上和向下键,并将提示符替换为以前的命令。

    sintax 就是rlwrap ./your-script

    【讨论】:

      【解决方案4】:

      对读取命令使用-e 选项(并确保将readline 配置为使用向上/向下箭头键循环浏览命令历史记录)。

      help read | less -p '-e'
      

      【讨论】:

      • 谢谢,这很有用,但除了命令历史记录中的输入之外,我还希望能够循环浏览以前的输入
      【解决方案5】:

      据我所知,没有。 "up" 和 "down" 和任何符号一样好(就此而言,C-p 和 C-n 在 bash 中的功能与 "up" 和 "down" 相同),并且可以作为你的一部分输入正在尝试阅读。

      也就是说,假设您的意思是内置的 bash read。您可以查看手册页中的任何选项,但我想不出任何可以满足您要求的 hack,至少现在不会……

      编辑:看起来很有趣。 @ 现在工作 & 没有 bash 或时间,但可以通过在 read 上设置 -n 1 来完成,然后检查您是否只是阅读“向上”或“向下”并使用 history 来获取所需的命令。您可能必须发明一个本地变量来计算“向上”和“向下”,然后从 history 获取具有适当偏移量的相关命令,将其输出到屏幕&如果下一个 read 返回一个空字符串,使用找到的命令。

      正如我所说,无法测试此 atm,也不知道它是否可以工作。

      【讨论】:

      • 有趣,我稍后会尝试并分享我的结果。实际上,我真的不需要命令历史记录,只需要能够访问以前的输入。感谢您的想法
      • 哦,那我想我误会了你。好吧,获取您在执行特定脚本期间的先前输入并没有太大不同,除了您可能还必须保留一个本地数组 var 或一些具有先前用户输入的临时文件并在何时相应地更新它接受输入。显然,如果您还需要脚本之前执行的输入,那么除了文件之外您别无选择,如果存储的命令有限制,它可能会很快变得复杂(bash 默认为 500) .检索可能是对tail 的简单调用。
      • 只有一件事,我如何检查输入是否是箭头键?我正在尝试类似:while true; do read -n 1 key; if [ $key == "\[A" ] ; then echo "up-arrow"; fi ; done
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-03
      • 2011-10-10
      • 2014-07-05
      相关资源
      最近更新 更多