Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enable the up/down arrow keys to show previous inputs when using `read`?

Tags:

bash

shell

I'm waiting for user input (using 'read') in an infinite loop and would like to have command history, that is being able to show previous inputs that were already entered, using the up and down arrow keys, instead of getting ^[[A and ^[[B. Is this possible?


Thanks to @l0b0 for your answer. It got me on the right direction. After playing with it for some time I've realized I also need the following two features, but I haven't managed to get them yet:

  • If I press up and add something to the previous command I would like to have the whole thing saved in the history, not just the addition. Example

    $ ./up_and_down
    Enter command: hello
    ENTER
    Enter command:
    Up
    Enter command: hello you
    ENTER
    Enter command:
    Up
    Enter command: you
    (instead of "hello you")

  • If I can't keep going up because I'm at the end of the history array, I don't want the cursor to move to the previous line, instead I want it to stay fixed.

This is what I have so far (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
like image 364
nachocab Avatar asked Jul 22 '11 10:07

nachocab


People also ask

How do I use arrow keys in Linux?

The way to scroll using the arrow keys in a terminal emulator is to hold down the shift key. This is because a terminal emulator send all keypresses to the application running in the terminal; holding shift down bypasses that. This can also be used with other keys like copy and paste.

How do I use arrow keys in JavaScript?

To detect the arrow key when it is pressed, use onkeydown in JavaScript. The button has key code. As you know the left arrow key has the code 37. The up arrow key has the code 38 and right has the 39 and down has 40.


1 Answers

Two solutions using the -e option to the read command combined with the builtin history command:

# 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
like image 56
micha Avatar answered Oct 11 '22 05:10

micha