read keys

About writing shell scripts and making the most of your shell
Forum rules
Topics in this forum are automatically closed 6 months after creation.
Locked
1000
Level 6
Level 6
Posts: 1018
Joined: Wed Jul 29, 2020 2:14 am

read keys

Post by 1000 »

Introduction
read command is specific,
a = this is one character
up arrow = these are three characters

I tried to change a script which is based on another script from the internet

Code: Select all

#!/bin/bash


#   https://unix.stackexchange.com/questions/179191/bashscript-to-detect-right-arrow-key-being-pressed
#   https://dirask.com/posts/Bash-detect-arrow-key-pressed-jvRlVj
#   man read
#   https://ss64.com/bash/read.html


GetPos() {
    IFS=';' read -sdR -p $'\E[6n' ROW COL
    Col=$(( ${COL#*[} -1 ))
    Row=$(( ${ROW#*[} -1 ))       
}

# Will print position
Func_Up()    { GetPos ; echo -n $Col ;}
Func_Down()  { GetPos ; echo -n $Row ;}
# Will move cursor
Func_Right() { GetPos ; tput cup "$Row" "$(( $Col + 1 ))" ;}
Func_Left()  { GetPos ; 
                if [[ "Col" -gt 0  ]] ; then
                    tput cup "$Row" "$(( $Col - 1 ))" 
                fi ;}

Func_Backspace() { 
    GetPos 
    # It may not be possible to read the content from the screen
    # But you can,
    # 1. Store each letter you typed into a variable,
    # 2. Edit a variable
    # 3. Move cursor to the beginning
    # 4. Clear the screen
    # 5. Print the variable again
    
    # Here I delete everything to the right of the cursor
    Func_Left ; tput ed
}
    
#while read -rsn1 ui; do
while read -rsN1 ui; do
    case "$ui" in
    $'\x1b')    # Handle ESC sequence.
        # Flush read. We account for sequences for Fx keys as
        # well. 6 should suffice far more then enough.
        read -rsn1 -t 0.1 tmp
        if [[ "$tmp" == "[" ]]; then
            read -rsn1 -t 0.1 tmp
            case "$tmp" in   
                "A") Func_Up ;;
                "B") Func_Down ;;
                "C") Func_Right ;;
                "D") Func_Left ;;
            esac
        else
            echo "$tmp"
        fi
        # Flush "stdin" with 0.1  sec timeout.
        # Creates extra letters when keys are pressed for a long time
#        read -rsn5 -t 0.1
        ;;
    # Other one byte (char) cases. Here only quit.
    #       showkey -a
    q) echo q ; break ;;
#    "") echo "Enter"  ;;  # the same for spacebar when is "read -n" instead "read -N"
#    $'\15') echo "Enter"  ;;  # Not working
    # https://stackoverflow.com/questions/2612274/bash-shell-scripting-detect-the-enter-key
    # https://stackoverflow.com/questions/3871729/transmitting-newline-character-n
#    $'\x0a') echo "Enter"  ;;  # I don't know from where is x0a , probably unicode
    $'\12') echo "Enter"  ;;
    $'\40') echo "Space Bar"  ;;
    $'\177') Func_Backspace ;;
    
    *) echo -n "$ui" ;;
    esac
done
The problem with this script is that,
when I hold down the "right arrow" and "left arrow" keys
I see sometimes

Code: Select all

^[[C                        ^[[D        ^[[C
I guess my keyboard is faster than my script. I can't find any other explanation.
Last edited by LockBot on Fri Sep 29, 2023 10:00 pm, edited 1 time in total.
Reason: Topic automatically closed 6 months after creation. New replies are no longer allowed.
1000
Level 6
Level 6
Posts: 1018
Joined: Wed Jul 29, 2020 2:14 am

Re: read keys

Post by 1000 »

The solution is not perfect, but for now I have no idea for a better one.
I saved the word to a variable.
When I move the cursor to the left
then I clear the screen first, and then I display the word.
In addition, I added the distance to move the cursor depending on how slow the script is.

Code: Select all

#!/bin/bash


#   https://unix.stackexchange.com/questions/179191/bashscript-to-detect-right-arrow-key-being-pressed
#   https://dirask.com/posts/Bash-detect-arrow-key-pressed-jvRlVj
#   man read
#   https://ss64.com/bash/read.html


Func_GetPos() {
    IFS=';' read -sdR -p $'\E[6n' ROW COL
    
    # When the script runs slower than the keyboard, "read" returns invalid values
    # So I used "if" to detect number
    if ! [[ "${ROW#*[}" =~ '^[0-9]+$' ]] ; then
        Row="$Row" 
    else
        Row=$(( ${ROW#*[} -1 )) 
    fi
          
    Col=$(( ${COL#*[} -1 ))
}

Func_Hide()  {
                    #------------------{
                    ## THIS IS FOR HIDING DEFECTS like ^[[C when script is slower
                    # move to begin
                    tput cup "$Row" "0" 
                    # clear line
                    tput ed
                    # Print variable again
                    #printf "%s" "$Word"
                    echo -n "$Word"
                    tput cup "$Row" "$Col"
                    #------------------}
}

# Will print position
Func_Up()    { Func_GetPos ; echo -n $Col ;}
Func_Down()  { Func_GetPos ; echo -n $Row ;}
# Will move cursor
Func_Right() { Func_GetPos ; Func_Hide ; tput cup "$Row" "$(( $Col + 1 ))" ;}
Func_Left()  { Func_GetPos ; 
                # The column must be a positive number. ( greater than -1 )
                # Otherwise, "tput" will return an error
                if [[ "Col" -gt 0  ]] ; then
                    Func_Hide
                    
                    End_Time=$(echo "$(date +%s%N) - $Start_Time" | bc)
                    
                    #echo $End_Time
                
                
                    # move according to changes
                    if [[ "$End_Time" -gt "127233244" ]] ; then 
                        # move to left
                        tput cup "$Row" "$(( $Col - 1 ))" 
                    else
                        # The column must be a positive number. ( greater than -1 )
                        # Otherwise, "tput" will return an error
                       if [[ "Col" -gt 20  ]] ; then
                           tput cup "$Row" "$(( $Col - 20 ))"
#                            echo "$Old_Col $Col"
                        fi
                    fi
                fi 
                }

Func_Backspace() { 
    # It may not be possible to read the content from the screen
    # But you can,
    # 1. Save each letter you typed into a variable,
    # 2. Edit a variable
    Func_GetPos
    Word=$( fold -w1 <<< "${Word}" | sed ${Col}d | tr -d "\n" )
    # 3. Move cursor to the beginning
    tput cup "$Row" 0 ; 
    # 4. Clear the screen
    tput ed ;
    # 5. Print the variable again
    echo -n "${Word}" ;
    # 6. Move cursor one to the left
    tput cup "$Row" $(( "$Col" -1 ))
    

    # Here I delete everything to the right of the cursor
    # https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
#    Func_Left ; tput ed
}


# Start script
reset
echo start

    # saving variables "Col" , "Row" in case of failure
    Row=1 ; Col=1
    
    Start_Time=$(date +%s%N)

#while read -rsn1 ui; do
while read -rsN1 ui; do

#    Func_GetPos
    #date +"%Y-%m-%d %T"
    
    case "$ui" in
    $'\x1b')    # Handle ESC sequence.
        # Flush read. We account for sequences for Fx keys as
        # well. 6 should suffice far more then enough.
        read -rsn1 -t 0.0001 tmp
        if [[ "$tmp" == "[" ]]; then
            read -rsn1 -t 0.0001 tmp
            case "$tmp" in   
                "A") Func_Up ;;
                "B") Func_Down ;;
                "C") Func_Right ;;
                "D") Func_Left ;;
            esac
#        else
#            echo "$tmp"
        fi
        # Flush "stdin" with 0.1  sec timeout.
        # Creates extra letters when keys are pressed for a long time
#        read -rsn5 -t 0.1
        ;;
    # Other one byte (char) cases. Here only quit.
    #       showkey -a
    q) echo q ; break ;;
#    "") echo "Enter"  ;;  # the same for spacebar when is "read -n" instead "read -N"
#    $'\15') echo "Enter"  ;;  # Not working
    # https://stackoverflow.com/questions/2612274/bash-shell-scripting-detect-the-enter-key
    # https://stackoverflow.com/questions/3871729/transmitting-newline-character-n
#    $'\x0a') echo "Enter"  ;;  #  x0a = LF in hex 
    $'\12') echo "Enter : Word = $Word"  ;;   # 12 = LF in Oct 
    $'\40') 
        Func_GetPos
        # First part
        P1_Word=${Word::$Col}
        # Rest
        P2_Word=${Word:$Col}
        Word="${P1_Word}${ui}${P2_Word}" 
        #  Move cursor to the beginning
        tput cup "$Row" 0 ;
        #  Clear the screen
        tput ed ; 
        printf "%s" "${Word}" ;
        tput cup "$Row" "$(($Col + 1))"
    ;;  # Space Bar" 
    $'\177') Func_Backspace ;;
    
    
    # Warning ! This is only example script, and above "q" character = exit 
    *) 
        Func_GetPos
        # First part
        P1_Word=${Word::$Col}
        # Rest
        P2_Word=${Word:$Col}
        Word="${P1_Word}${ui}${P2_Word}" 
        #  Move cursor to the beginning
        tput cup "$Row" 0 ;
        #  Clear the screen
        tput ed ; 
        printf "%s" "${Word}" ;
        tput cup "$Row" "$(($Col + 1))"
        ;;
    esac
    

    #echo $End_Time
    Start_Time=$(date +%s%N)
    
    
    # Script slowdown simulation for printing extra unwanted characters
    # longer time = more unwanted characters or errors from Func_Func_GetPos
    sleep 0.1
done

Edited
I noticed that I can also change key repeat delay,
in a graphical environment
and with command " xset r rate "
http://xahlee.info/linux/linux_set_key_repeat_rate.html
1000
Level 6
Level 6
Posts: 1018
Joined: Wed Jul 29, 2020 2:14 am

Re: read keys

Post by 1000 »

I made one mistake.

Code: Select all

if [[ "4" =~ '^[0-9]+$' ]] ; then echo number ;else echo fail ;fi
fail

Maybe something like this will work better for detect only numbers
( without quotation marks " or ' )

Code: Select all

if [[ "4" =~ ^[0-9]+$ ]] ;then echo numb ;else echo fail ;fi
numb
User avatar
Termy
Level 12
Level 12
Posts: 4254
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: read keys

Post by Termy »

1000 wrote: Tue Apr 04, 2023 8:23 am [...]
With [[, glob pattern matching and REGEX are both performed by the shell, therefore the shell needs to interpret what you place. When you quoted the REGEX string, you neutered it.

Code: Select all

if [[ 'test string' =~ ^test\ ]]; then
	echo True
else
	echo False
fi
In the above example, you will see True, because the string 'test ' is at the beginner of the string. If you single- or double-quote the entire REGEX string, it'll take that as a literal string without any REGEX parsing, so it'll literally take the string given to BASH, then look for '^test\ ', which of course doesn't exist. This applies to glob pattern matching and glob filename pattern matching. There are some exceptions which might catch you out, such as find(1)'s glob pattern matching, which is actually not handled by BASH. Globbing is not exclusive to the shell.

I'm not sure that all this is relevant to your issue, but it's something I felt worth mentioning.
I'm also Terminalforlife on GitHub.
Locked

Return to “Scripts & Bash”