Bash 使用“读取”时,如何启用向上/向下箭头键显示以前的输入?

Bash 使用“读取”时,如何启用向上/向下箭头键显示以前的输入?,bash,shell,Bash,Shell,我正在无限循环中等待用户输入(使用'read'),并希望有命令历史记录,即能够使用上下箭头键显示已输入的先前输入,而不是获取^[[A和^[[B]。这可能吗 感谢@l0b0的回答。它让我走上了正确的方向。在玩了一段时间后,我意识到我还需要以下两个功能,但我还没有成功获得它们: 如果我按下up键并向上一个命令添加内容,我希望将整个内容保存在历史记录中,而不仅仅是添加内容。例如 美元/上涨和下跌 输入命令:hello 输入 输入命令: 向上 输入命令:您好 输入 输入命令: 向上 输入命令:您 (

我正在无限循环中等待用户输入(使用'read'),并希望有命令历史记录,即能够使用上下箭头键显示已输入的先前输入,而不是获取^[[A和^[[B]。这可能吗


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

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

    美元/上涨和下跌
    输入命令:hello
    输入
    输入命令:
    向上
    输入命令:您好
    输入
    输入命令:
    向上
    输入命令:您
    (而不是“你好”)

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

这就是我到目前为止所做的(上下):

!/usr/bin/env bash
set-o名词set-o errexit-o pipefail
阅读历史{
局部字符
本地字符串
本地esc=$'\e'
本地向上=$'\e[A'
本地向下=$'\e[B'
本地清除线=$'\r\e[K'
本地历史=()
本地-i历史_索引=0
#一次读一个字符
当IFS=”“read-p”输入命令时:“-n1-s char;do
如果[[“$char”==“$esc”]];则
#获取转义序列的其余部分(总共3个字符)
读取时-n2-s休息;执行
char+=“$rest”
打破
完成
fi
如果[[“$char”==“$up”&&&$history_index>0]];则
历史索引+=-1
echo-ne$clear_line${history[$history_index]}
elif[“$char”==“$down”&&$history_index<$(${{history[@]}-1))];然后
历史指数+=1
echo-ne$clear_line${history[$history_index]}
elif[-z“$char”]];然后#用户按ENTER键
回声
历史+=(“$string”)
串=
历史索引=${#历史[@]}
其他的
echo-n“$char”
字符串+=“$char”
fi
完成
}
阅读历史
据我所知,“向上”和“向下”都是和任何符号一样好的符号(就此而言,C-p和C-n在功能上与bash中的“向上”和“向下”相同),并且可以作为您试图阅读的内容的一部分进行输入

也就是说,假设您指的是bash内置的
read
。您可以查看手册页中的任何选项,但我想不出任何可以满足您需要的黑客,至少现在不行

编辑:似乎很有趣。@现在就开始工作&没有bash或时间,但可以通过将
-n1
设置为
read
,然后检查您是否只读取了“up”或“down”,并使用
history
获取所需的命令来完成。您可能需要发明一个本地变量来计算“up”和“down”s、 然后从
history
获取具有适当偏移量的相关命令,将其输出到屏幕&如果下一个
read
返回空字符串,则使用找到的命令

正如我所说,我无法测试这个atm,也不知道它是否能工作。

我用它在不支持它的程序中启用readline功能。可能你可以试试这个。rlwrap代表readline wrapper。这个命令截取你的向上键和向下键,并用以前的命令替换提示


sintax只是
rlwrap./您的脚本使用
-e
选项读取命令(并确保
readline
配置为使用向上/向下箭头键循环命令历史记录)


有趣的问题-以下是目前为止的结果:

#!/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

使用
读取
命令的
-e
选项以及内置的
历史
命令的两种解决方案:

# 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

有趣的是,我稍后会尝试一下并分享我的结果。事实上,我并不真正需要命令历史记录,只需要能够访问以前的输入。感谢您的想法,那么我想我误解了您。嗯,在执行特定脚本期间获得您以前的输入不会有太大的不同,只是您需要可能还需要保留一个本地数组变量或某个临时文件以及以前的用户输入,并在接受输入时相应地更新它。显然,如果您还需要以前执行的脚本的输入,那么除了文件之外,您将没有其他选项,如果存储的命令有限制,那么它可能会很快变得复杂(bash默认设置为@500)。检索可能只需调用
tail
。有一件事,我如何检查输入是否是箭头键?我正在尝试这样的操作:
为true时;读取-n1键;如果[$key==“\[a”];则回显“向上箭头”;fi;done
谢谢,这很有用,但我也希望能够循环使用以前的输入,除了命令历史记录中的输入,这正是我要查找的。您能解释一下您的答案吗?具体地说:您是否将输入读作两个单独的字符?为什么?什么是
\x1b
转义码?您为什么要准备nd a
$
?您是设置
IFS
还是清空它?
本地字符=;
本地字符;
之间有什么区别吗?您能解释一下
读取-r-n1-s字符中的选项吗?我如何阅读
本地
中的选项(如
-a
-I
)?在代码中添加了一些注释。IFS=将其设置为空字符串以避免。
local char
只是声明函数的一个局部变量,但之后它没有定义等号(此时它是空字符串)。至于
$”
read
/
local
选项,请参见
#!/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
# 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