Is it possible to case arrow keys in a bash script to run a certain set of commands if the up/left arrow keys are pressed, and a certain set if the down/right arrow keys are pressed? I am trying to have a way to switch between users quickly by using the arrow keys while it is displaying the data, using this script to read the data from.
function main() # The main function that controls the execution of all other functions
{
mkdir -p ~/usertmp # Make a new temporary user directory if it doesn't exist
touch ~/last_seen_output.txt # Create the output file if it doesn't exist
cat /dev/null > ~/last_seen_output.txt # Make sure that the output file is empty
gather # Call the "gather" function
total=$((`wc -l ~/usertmp/user_list.txt|awk '{print $1}'`-1)) # Calculate the total amount of lines and subtract 1 from the result
echo Current Time: `date +%s` > ~/last_seen_output.txt # Print the current time to the output file for later reference
echo "" > ~/last_seen_output.txt # Print a blank line to the output file
if [ $log -eq 1 ]
then
# If it is enabled, then delete the old backups to prevent errors
while [ $line_number -le $total ]
do
line_number=$((line_number+1)) # Add 1 to the current line number
calculate # Call the "calculate" function
hms # Call the "hms" function to convert the time in seconds to normal time
log
done
else
while [ $line_number -le $total ]
do
line_number=$((line_number+1)) # Add 1 to the current line number
calculate # Call the "calculate" function
hms # Call the "hms" function to convert the time in seconds to normal time
echo "Displaying, please hit enter to view the users one by one."
read # Wait for user input
if [ "$log_while_displaying" ]
then
log
display
else
display
fi
done
fi
}
https://github.com/jbondhus/last-seen/blob/master/last-seen.sh is the complete script.
The read command commented as "wait for user input" is the command that you hit enter to go to the next user for. Basically, what this script does it list users and the time that has passed since each user logged in. I am trying to switch between each user being displayed by using the arrow keys. I figured that it might be possible to use a case statement to case the key input. To reiterate my point, I'm not sure if this is possible. If it isn't, can anyone think of another way to do this?
$1 means an input argument and -z means non-defined or empty. You're testing whether an input argument to the script was defined when running the script.
bash [filename] runs the commands saved in a file. $@ refers to all of a shell script's command-line arguments. $1 , $2 , etc., refer to the first command-line argument, the second command-line argument, etc. Place variables in quotes if the values might have spaces in them.
Alternatively referred to as cursor keys, direction keys, and navigation keys, the arrow keys are usually located between the standard section and the numeric pad on computer keyboards. It is made up of four keys: the left arrow (back arrow), up arrow, down arrow, and the right arrow (forward arrow).
&>word (and >&word redirects both stdout and stderr to the result of the expansion of word. In the cases above that is the file 1 . 2>&1 redirects stderr (fd 2) to the current value of stdout (fd 1).
You can read arrow keys as well as other keys without any unusual commands; you just need to conditionally add a second read
call:
escape_char=$(printf "\u1b")
read -rsn1 mode # get 1 character
if [[ $mode == $escape_char ]]; then
read -rsn2 mode # read 2 more chars
fi
case $mode in
'q') echo QUITTING ; exit ;;
'[A') echo UP ;;
'[B') echo DN ;;
'[D') echo LEFT ;;
'[C') echo RIGHT ;;
*) >&2 echo 'ERR bad input'; return ;;
esac
As mentioned before, the cursor keys generate three bytes - and keys like home/end even generate four! A solution I saw somewhere was to let the initial one-char read() follow three subsequent one-char reads with a very short timeout. Most common key sequences can be shown like this f.e.:
#!/bin/bash
for term in vt100 linux screen xterm
{ echo "$term:"
infocmp -L1 $term|egrep 'key_(left|right|up|down|home|end)'
}
Also, /etc/inputrc contains some of these with readline mappings.. So, answering original question, here's a snip from that bash menu i'm just hacking away at:
while read -sN1 key # 1 char (not delimiter), silent
do
# catch multi-char special key sequences
read -sN1 -t 0.0001 k1
read -sN1 -t 0.0001 k2
read -sN1 -t 0.0001 k3
key+=${k1}${k2}${k3}
case "$key" in
i|j|$'\e[A'|$'\e0A'|$'\e[D'|$'\e0D') # cursor up, left: previous item
((cur > 1)) && ((cur--));;
k|l|$'\e[B'|$'\e0B'|$'\e[C'|$'\e0C') # cursor down, right: next item
((cur < $#-1)) && ((cur++));;
$'\e[1~'|$'\e0H'|$'\e[H') # home: first item
cur=0;;
$'\e[4~'|$'\e0F'|$'\e[F') # end: last item
((cur=$#-1));;
' ') # space: mark/unmark item
array_contains ${cur} "${sel[@]}" && \
sel=($(array_remove $cur "${sel[@]}")) \
|| sel+=($cur);;
q|'') # q, carriage return: quit
echo "${sel[@]}" && return;;
esac
draw_menu $cur "${#sel[@]}" "${sel[@]}" "$@" >/dev/tty
cursor_up $#
done
You can use read -n 1
to read one character then use a case
statement to choose an action to take based on the key.
On problem is that arrow keys output more than one character and the sequence (and its length) varies from terminal to terminal.
For example, on the terminal I'm using, the right arrow outputs ^[[C
. You can see what sequence your terminal outputs by pressing Ctrl-V Right Arrow. The same is true for other cursor-control keys such as Page Up and End.
I would recommend, instead, to use single-character keys like <
and >
. Handling them in your script will be much simpler.
read -n 1 key
case "$key" in
'<') go_left;;
'>') go_right;;
esac
# This will bind the arrow keys
while true # Forever = until ctrl+c
do
# r: backslash is not for escape
# s: silent
# "n x": read x chars before returning
read -rsn 1 t
case $t in
A) echo up ;;
B) echo down ;;
C) echo right ;;
D) echo left ;;
esac
done
Not sure if this answer the question directly, but I think it's related - I was wandering where do those codes come from, and I finally found:
It's a bit difficult to read at first; for left arrow, lookup "LEFT 4" in the "Key" column, and for the sequence that bash
sees, look up the 5th ("keymap" - "normal") column, where it is written as "[D 1b 5b 44" - which are the three bytes (27, 91, 68) representing this key.
Finding the thread How to read arrow keys on really old bash? - The UNIX and Linux Forums, inspired me to write a short one-liner which dumps the key codes of keys pressed. Basically, you press a key, then Enter (to trigger ending of read
), and then use hexdump
to output what read
has saved (and finally hit Ctrl-C to exit the loop):
$ while true; do read -p?; echo -n $REPLY | hexdump -C; done
?^[[D
00000000 1b 5b 44 |.[D| # left arrow
00000003
?^[[C
00000000 1b 5b 43 |.[C| # right arrow
00000003
?^[[1;2D
00000000 1b 5b 31 3b 32 44 |.[1;2D| # Shift+left arrow
00000006
?^[[1;2C
00000000 1b 5b 31 3b 32 43 |.[1;2C| # Shift+right arrow
00000006
?^C
So, while arrow keys require 3 bytes - Shift+arrow keys require 6! However, seemingly all these sequence start with 0x1b (27), so one could possibly check for this value for read -n1
, before reading any more bytes; also 5b
remains a second byte in multi-byte sequence for the "normal" and "shift/NUM-Lock" columns of the table above.
Edit: much easier and proper way to scan for terminal codes of pressed keys in Linux is via showkey:
$ showkey
Couldn't get a file descriptor referring to the console
$ showkey -h
showkey version 1.15
usage: showkey [options...]
valid options are:
-h --help display this help text
-a --ascii display the decimal/octal/hex values of the keys
-s --scancodes display only the raw scan-codes
-k --keycodes display only the interpreted keycodes (default)
$ sudo showkey -a
Press any keys - Ctrl-D will terminate this program
^[[A 27 0033 0x1b
91 0133 0x5b
65 0101 0x41
^[[B 27 0033 0x1b
91 0133 0x5b
66 0102 0x42
^[[A 27 0033 0x1b
91 0133 0x5b
65 0101 0x41
^[[D 27 0033 0x1b
91 0133 0x5b
68 0104 0x44
^[[C 27 0033 0x1b
91 0133 0x5b
67 0103 0x43
^C 3 0003 0x03
^M 13 0015 0x0d
^D 4 0004 0x04
Using eMPee584
answer I think I came up a good solution for you.
Its output is much the same as user3229933
answer but will not be triggered by shift keys and will work in most terminals.
It has UP DOWN LEFT RIGHT HOME and END Keys
Press 'q' to quit
Most of this is thanks to eMPee584
you may need to change '-sn1' to '-sN1' if you get an error like illegal option n
.
#!/bin/bash
while read -sn1 key # 1 char (not delimiter), silent
do
read -sn1 -t 0.0001 k1 # This grabs all three symbols
read -sn1 -t 0.0001 k2 # and puts them together
read -sn1 -t 0.0001 k3 # so you can case their entire input.
key+=${k1}${k2}${k3}
case "$key" in
$'\e[A'|$'\e0A') # up arrow
((cur > 1)) && ((cur--))
echo up;;
$'\e[D'|$'\e0D') # left arrow
((cur > 1)) && ((cur--))
echo left;;
$'\e[B'|$'\e0B') # down arrow
((cur < $#-1)) && ((cur++))
echo down;;
$'\e[C'|$'\e0C') # right arrow
((cur < $#-1)) && ((cur++))
echo right;;
$'\e[1~'|$'\e0H'|$'\e[H') # home key:
cur=0
echo home;;
$'\e[4~'|$'\e0F'|$'\e[F') # end key:
((cur=$#-1))
echo end;;
q) # q: quit
echo Bye!
exit;;
esac
done
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With