在bash中如何使用箭头键进行大小写转换

14

在bash脚本中,是否可以将箭头键用作触发器,当按下向上/向左箭头键时运行一组指令,按下向下/向右箭头键时运行另一组指令?我想要一种方法来在显示数据时使用箭头键快速切换用户,使用此脚本读取数据。

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 是完整的脚本。
read命令被注释为“等待用户输入”,它是您按Enter键以继续下一个用户的命令。基本上,此脚本会列出用户及其登录后经过的时间。我正试图使用箭头键在每个用户之间进行切换。我想这可能可以使用case语句来处理键盘输入。重申一下,我不确定这是否可行。如果不行,有人能想到其他方法吗?
11个回答

25

你可以像读取其他按键一样读取箭头按键,无需任何不寻常的命令;你只需要有条件地添加第二个read调用即可:

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

非常好,谢谢!不确定为什么这不更受欢迎。易于阅读和工作。 - Martin Mucha
几乎完美...如果你进行了第二次读取-rsn4,你也可以识别F*键,但是你需要在情况下添加~ - 请参见我的扩展答案。 - Paul Hedderly
Paul Hedderly,我不确定是否值得添加一个读取计时器(-t 0.001)来支持OP没有要求的东西。这是一项简单的增强功能,但引入了另一个可能发生故障的维度,似乎并没有使解决方案更加完美:shrug: - JellicleCat

18

如前所述,光标键会生成三个字节 - 而像home/end这样的键甚至会生成四个字节!我曾在某处看到的解决方案是让初始的单字符read()操作,在三个随后的单字符读取操作之后,加上一个非常短的超时时间。最常见的键序列可以像这样显示:

#!/bin/bash
for term in vt100 linux screen xterm
  { echo "$term:"
    infocmp -L1 $term|egrep 'key_(left|right|up|down|home|end)'
  }

此外,/etc/inputrc 中包含一些 readline 映射。回答原问题,这是我正在砍掉的 bash 菜单中的一部分代码片段:
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

+1虽然这并没有真正解决问题。我会选择@DennisWilliamson的最终建议。 - tripleee
对于Mac用户,-sN1 需要改为 -sn1。此外,-t 0.0001 不起作用。它会抛出 read: 0.0001: invalid timeout specification 的错误。@eMPee584您知道如何解决Mac中的分数超时问题吗? - Moon

12
# 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

1
不错的技巧!但它也会在按Shift-a时触发,而且是误报。 - xebeche
1
我留下的另一种答案使用了 read,但不会触发其他对 AB 等的使用。 - JellicleCat

11

你可以使用read -n 1读取一个字符,然后使用case语句根据所读取的按键来选择相应的操作。

问题在于方向键会输出多个字符,并且这个序列(以及它的长度)因终端而异。

例如,在我正在使用的终端上,右箭头输出为^[[C。你可以通过按下Ctrl-VRight Arrow来查看你的终端输出的序列。其他控制光标的键如Page UpEnd也是如此。

我建议使用单个字符键,如<>。在脚本中处理它们会更简单。

read -n 1 key

case "$key" in
    '<') go_left;;
    '>') go_right;;
esac

@JonathanBondhus:它们是函数名称或其他命令或语句的占位符,您将提供这些内容。 - Dennis Williamson
还有一件事,我更新了我的脚本,可以在这里看到,并且现在已经分配了“,”用于返回用户,“。”用于前进。我这样做是为了让用户不必按住Shift键。到目前为止,我正在使用pastebin.com/MiPA5jJD作为解析键输入的函数,并且我想更改它,以便它使用箭头键而不是“,”和“。”来进行后退和前进。我尝试使用keycode,^[[D和^[[C作为左右键,如'^[[D'和'^[[C',但仍然无法正常工作。 - Jonathan Bondhus
别试图使用箭头键。为了做到这点,你需要在Python脚本中使用一个诅咒模块或类似的东西,而不是尝试使用Bash脚本。关于你的脚本的一些评论:你可以通过使用 (( var+=value ))(( var++ )) 来提高可读性并减小代码的大小,而不是使用 var=$((var+value))。例如: (( total_time -= mo * 2629746 ))。没必要用 var=$((0)),只需使用 var=0 即可。整数比较:while (( line_number <= total ))。对于命令替换,请使用 $(),而不是反引号。你不需要awk:wc -l < filename - Dennis Williamson
谢谢,我今年17岁,只学了大约3个月的Bash编程。 - Jonathan Bondhus
1
你开了一个非常好的头。在我之前的评论中,我说了“cursed module”,那是个打错字。它应该是“curses模块”。 - Dennis Williamson
嗯,您可以在Bash中完美地处理箭头键……就像处理其他转义序列一样。 - Reishin

9

不确定这是否直接回答了问题,但我认为它与此相关 - 我一直在想这些代码来自哪里,最终找到了:

一开始有点难读;对于左箭头,请查找“Key”列中的“LEFT 4”,对于bash看到的序列,请查找第5个(“keymap” - “normal”)列,其中写着“[D 1b 5b 44” - 这是表示此键的三个字节(27、91、68)。

找到线程如何在真正旧的bash上阅读箭头键?-UNIX和Linux论坛,激发了我编写一个简短的单行命令,可转储按下的键的键代码。基本上,您按下一个键,然后按Enter(触发read的结束),然后使用hexdump输出read保存的内容(最后按Ctrl-C退出循环):

$ 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

因此,箭头键需要3个字节,但Shift+箭头键需要6个字节!然而,所有这些序列似乎都以0x1b(27)开始,因此可以在读取更多字节之前检查read -n1的值是否为此值; 此外,5b仍然是上表中“正常”和“shift / NUM-Lock”列的多字节序列的第二个字节。
编辑:在Linux中扫描按下键的终端代码的更简单和正确的方法是通过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

5

使用eMPee584的答案,我认为我给你想出了一个好的解决方案。 它的输出与user3229933的答案大致相同,但不会被shift键触发,并且在大多数终端中都可以工作。

它拥有上下左右Home和End键 按下 'q' 退出 这大部分要感谢eMPee584

如果出现非法选项n之类的错误,您可能需要将'-sn1'更改为'-sN1'。

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

0

对于任何寻找兼容Mac版本的人,它还可以处理按住Shift键以及Enter和Space:

#!/bin/bash

ESC=$'\033'
SHIFT=$'[1;2'
# distinguish between enter and space
IFS=''

while true; do
    read -rsn1 a
    # is the first character ESC?
    if [[ $ESC == $a ]]; then
        read -rsn2 b
        # does SHIFT start with the next two characters?
        if [[ $SHIFT == "$b"* ]]; then
            read -rsn3 c
        fi
    fi

    input=$a$b$c
    unset b c

    case $input in
        $ESC[A) echo UP ;;
        $ESC[B) echo DOWN ;;
        $ESC[C) echo RIGHT ;;
        $ESC[D) echo LEFT ;;

        $ESC$SHIFT'A') echo SHIFT UP ;;
        $ESC$SHIFT'B') echo SHIFT DOWN ;;
        $ESC$SHIFT'C') echo SHIFT RIGHT ;;
        $ESC$SHIFT'D') echo SHIFT LEFT ;;

        '') echo ENTER ;;
        ' ') echo SPACE ;;

        q) break ;;
    esac
done

0

这里是bash的另一种readkey变体。

但首先需要注意(与Linux相关):

read -rsN 1read -rsn 1 - 第二个变体将不区分转义、回车或空格。从@Paul Hedderly的答案中可以看出。

策略:

  • 扫描取决于第一个、第三个和第五个字节的变化
  • 我们扫描扩展键码,直到我们碰到“~”/ dec: 126 byte
  • 对于第三个字节dec 49,应该以不同的方式处理
  • 由于我们获取输入的方式,很难跟踪ESC按钮本身,因此我们采用了解决方法,如果我们获取到“27 27”序列,则表示按下了2次ESC按钮

注:

  • 这段代码应该能够返回任何按钮的扫描码。
    • Shift + Key
    • Alt + Key
  • keyCode 是一个数组,其中包含整个按钮扫描序列
  • 取消注释 # echo "Key: ${keyCode[*]}" 以查看按钮扫描码
  • 这不是理想或完美的东西,但它可以工作。
  • 代码使用 bash,并且可能与 dash、sh 等不兼容
  • 在 Windows 终端 ssh 中测试了 Linux 环境。
    #!/bin/bash
    
    readKey(){
        read -rsN 1 _key
        printf %d "'${_key}"   # %x for hex
    }
    
    demo(){
        local keyCode=(0)
    
        while [[ ${keyCode[0]} -ne 10 ]]; do  #exit on enter
            local keyCode=("$(readKey)") # byte 1
            if [[ ${keyCode[0]} -eq 27 ]]; then # escape character 
                local keyCode+=("$(readKey)") # byte 2
                if [[ ${keyCode[-1]} -ne 27 ]]; then # checking if user pressed actual
                    local keyCode+=("$(readKey)")  # byte 3
                    
                    if [[ "51 50 48 52 53 54" =~ (^|[[:space:]])"${keyCode[2]}"($|[[:space:]]) ]]; then
                        while [[ ${keyCode[-1]} -ne 126 ]]; do
                            local keyCode+=("$(readKey)")    
                        done
                    fi
                    if [[ "49" =~ (^|[[:space:]])"${keyCode[2]}"($|[[:space:]]) ]]; then
                        local keyCode+=("$(readKey)")  # byte 4
                        [[ ${keyCode[-1]} -ne 126 ]] && local keyCode+=("$(readKey)") # byte 5
                        [[ ${keyCode[-1]} -eq 59 ]] && local keyCode+=("$(readKey)") # byte 5 check
                        [[ ${keyCode[-1]} -ne 126 ]] && local keyCode+=("$(readKey)")
                    fi
                fi
            fi
            # echo "Key: ${keyCode[*]}"
            case "${keyCode[*]}" in 
                "27 91 65") echo "UP";;
                "27 91 66") echo "DOWN";;
                "27 91 67") echo "RIGHT";;
                "27 91 68") echo "LEFT";;
            esac
        done
    }

demo

0

这是我编写的一个函数,用于将特殊键转换为可读的关键字。

例如:

  • < -> LEFT
  • Shift + > -> SHIFT_RIGHT
  • Ctrl + Alt + Home -> CTRL_ALT_HOME
  • Ctrl + Alt + Shift + Page Up -> CTRL_ALT_SHIFT_PAGEUP
  • .....
#!/usr/bin/env bash

shopt -s extglob

#keyOf <KEY>
keyOf() {
  local key=
  local mod=

  case "$1" in
    $'\177' ) key=BACKSPACE ;;
      $'\b' ) key=CTRL_BACKSPACE ;;
  $'\E\177' ) key=ALT_BACKSPACE ;;
    $'\E\b' ) key=CTRL_ALT_BACKSPACE ;;

    $'\t' ) key=TAB ;;
  $'\E[Z' ) key=SHIFT_TAB ;;

  $'\E' ) key=ESCAPE ;;
    ' ' ) key=SPACE ;;
  esac

  if [ -z "${key:+x}" ] && [ "${1:0:1}" = $'\E' ]; then
    case "${1#$'\E'}" in
    \[??(?)\;2? ) mod=SHIFT_ ;;
    \[??(?)\;3? ) mod=ALT_ ;;
    \[??(?)\;4? ) mod=ALT_SHIFT_ ;;
    \[??(?)\;5? ) mod=CTRL_ ;;
    \[??(?)\;6? ) mod=CTRL_SHIFT_ ;;
    \[??(?)\;7? ) mod=CTRL_ALT_ ;;
    \[??(?)\;8? ) mod=CTRL_ALT_SHIFT_ ;;
    esac

    case "${1#$'\E'}" in
    OA | \[?(1\;?)A ) key="${mod}UP" ;;
    OB | \[?(1\;?)B ) key="${mod}DOWN" ;;
    OC | \[?(1\;?)C ) key="${mod}RIGHT" ;;
    OD | \[?(1\;?)D ) key="${mod}LEFT" ;;

    OP | \[?(1\;?)P ) key="${mod}F1" ;;
    OQ | \[?(1\;?)Q ) key="${mod}F2" ;;
    OR | \[?(1\;?)R ) key="${mod}F3" ;;
    OS | \[?(1\;?)S ) key="${mod}F4" ;;
        \[15?(\;?)~ ) key="${mod}F5" ;;
        \[17?(\;?)~ ) key="${mod}F6" ;;
        \[18?(\;?)~ ) key="${mod}F7" ;;
        \[19?(\;?)~ ) key="${mod}F8" ;;
        \[20?(\;?)~ ) key="${mod}F9" ;;
        \[21?(\;?)~ ) key="${mod}F10" ;;
        \[23?(\;?)~ ) key="${mod}F11" ;;
        \[24?(\;?)~ ) key="${mod}F12" ;;

      \[?(?(1)\;?)F ) key="${mod}END" ;;
      \[?(?(1)\;?)H ) key="${mod}HOME" ;;

         \[2?(\;?)~ ) key="${mod}INSERT" ;;
         \[3?(\;?)~ ) key="${mod}DELETE" ;;

         \[5?(\;?)~ ) key="${mod}PAGEUP" ;;
         \[6?(\;?)~ ) key="${mod}PAGEDOWN" ;;
    esac
  fi
  
  printf '%s' "${key:-$1}"
}



while read -rsN1 c; do
  d=
  read -t 0.001 -rsd $'\0' d
  c+="$d"
  printf "%q \t%q\n" "$c" "$(keyOf "$c")"
done

注意:仅在Windows(MSYS2/MingW64)上进行了测试,但可以轻松修改。


0

为了扩展JellicleCat的答案:

#!/bin/bash
escape_char=$(printf "\u1b")
read -rsn1 mode # get 1 character
if [[ $mode == $escape_char ]]; then
    read -rsn4 -t 0.001 mode # read 2 more chars
fi
case $mode in
    '') echo escape ;;
    '[a') echo UP ;;
    '[b') echo DOWN ;;
    '[d') echo LEFT ;;
    '[c') echo RIGHT ;;
    '[A') echo up ;;
    '[B') echo down ;;
    '[D') echo left ;;
    '[C') echo right ;;
    '[2~') echo insert ;;
    '[7~') echo home ;;
    '[7$') echo HOME ;;
    '[8~') echo end ;;
    '[8$') echo END ;;
    '[3~') echo delete ;;
    '[3$') echo DELETE ;;
    '[11~') echo F1 ;;
    '[12~') echo F2 ;;
    '[13~') echo F3 ;;
    '[14~') echo F4 ;;
    '[15~') echo F5 ;;
    '[16~') echo Fx ;;
    '[17~') echo F6 ;;
    '[18~') echo F7 ;;
    '[19~') echo F8 ;;
    '[20~') echo F9 ;;
    '[21~') echo F10 ;;
    '[22~') echo Fy ;;
    '[23~') echo F11 ;;
    '[24~') echo F12 ;;
    '') echo backspace ;;
    *) echo $mode;;
esac

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接