【问题标题】:Casing arrow keys in bash在bash中套管箭头键
【发布时间】:2023-03-14 19:39:02
【问题描述】:

如果按下向上/向左箭头键,是否可以在 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 是完整的脚本。

注释为“等待用户输入”的读取命令是您按回车键转到下一个用户的命令。基本上,这个脚本的作用是列出用户以及自每个用户登录以来经过的时间。我正在尝试使用箭头键在显示的每个用户之间切换。我认为可以使用 case 语句来区分关键输入。重申我的观点,我不确定这是否可能。如果不是,谁能想到另一种方法来做到这一点?

【问题讨论】:

    标签: bash


    【解决方案1】:

    您可以阅读箭头键以及其他键而无需任何异常命令;您只需要有条件地添加第二个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
    

    【讨论】:

    • 非常好,谢谢!不知道为什么这不更高。简单易读的工作。
    • 几乎完美...如果您第二次读取 -rsn4,您还可以识别 F* 键,但您需要在案例中添加 ~ - 请参阅我的扩展答案
    • Paul Hedderly,我只是不确定是否值得添加一个读取计时器 (-t 0.001) 只是为了支持 OP 没有要求的东西。这是对代码的简单增强,但是引入一个可能发生故障的维度似乎并没有使解决方案更接近完美:耸耸肩:
    【解决方案2】:

    以上答案都不适合我!我不得不从这个线程中的许多答案以及通过谷歌进行的其他搜索中获取点点滴滴。我花了大约一个小时来编造这个。

    我正在运行 Ubuntu 20.04 LTS,这对我有用(尽管它可能并不完美,因为我不得不“破解”它):

    waitkey() {
      local end=""
      local key=""
    
      echo
      echo "   Press ESC ... "
    
      while [ "$end" == "" ]; do
        read -rsn1 key
        case "$key" in
          $'\x1b')
            local k=""
            # I'm not sure why I have to do this if statement,
            # but without it, there are errors.  took forever
            # to figure out why 'read' would dump me outta the script
            if [ "$IFS" ]; then
              read -rsn1 -t 0.1 holder && k="$holder"
            else
              IFS=read -rsn1 -t 0.1 holder && k="$holder"
            fi 
    
            if [ "$k" == "[" ]; then
              read -rsn1 -t 0.1 holder && kk="$holder"
    
              ##############################
              # you put your arrow code here
              #
              # eg:
              #  case "$kk" in
              #    "A") echo "up arrow!" ;; # do something ...
              #  esac
              ##############################
            elif [ "$k" == "O" ]; then
              read -rsn1 -t 0.1 holder && kk="$holder"
    
              # I am honestly not knowing what this is for
            elif [ "$k" == "" ]; then
              end=1
            fi
        esac
      done
    }
    

    【讨论】:

      【解决方案3】:

      扩展 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
      

      【讨论】:

        【解决方案4】:

        使用eMPee584 回答我想我为你想出了一个很好的解决方案。 其输出与user3229933 答案大致相同,但不会由 shift 键触发,适用于大多数终端。

        它有 UP DOWN LEFT RIGHT HOME 和 END 键 按“q”退出 这大部分归功于eMPee584

        如果您收到类似illegal option 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
        

        【讨论】:

          【解决方案5】:
          # This will bind the arrow keys
          
          while true
          do
              read -r -sn1 t
              case $t in
                  A) echo up ;;
                  B) echo down ;;
                  C) echo right ;;
                  D) echo left ;;
              esac
          done
          

          【讨论】:

          • 好招!但它也会在 Shift-a 上触发,但事实并非如此。
          • 我留下的另一个答案使用read,但不会触发AB等的其他用途。
          【解决方案6】:

          不确定这是否直接回答了这个问题,但我认为它是相关的 - 我在徘徊这些代码来自哪里,我终于找到了:

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

          找到线程How to read arrow keys on really old bash? - The UNIX and Linux Forums,启发我写了一个简短的单行代码,它转储了按下的键的键码。基本上,你按下一个键,然后回车(触发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 仍然是上表中“正常”和“移位/数字锁定”列的多字节序列中的第二个字节。


          编辑:通过showkeyshowkey

          $ 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
          

          【讨论】:

            【解决方案7】:

            如前所述,光标键生成三个字节 - 而像 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 的最终建议。
            • 对于 Mac 用户,-sN1 需要为 -sn1。此外,-t 0.0001 不起作用。它抛出read: 0.0001: invalid timeout specification 错误。 @eMPee584 你知道如何解决 Mac 中的部分超时问题吗?
            【解决方案8】:

            您可以使用read -n 1 读取一个字符,然后使用case 语句根据键选择要执行的操作。

            问题是箭头键输出多个字符,并且序列(及其长度)因终端而异。

            例如,在我使用的终端上,右箭头输出^[[C。您可以通过按 Ctrl-V 右箭头 查看终端输出的顺序。 Page UpEnd 等其他光标控制键也是如此。

            我建议改为使用单字符键,例如 &lt;&gt;。在脚本中处理它们会简单得多。

            read -n 1 key
            
            case "$key" in
                '<') go_left;;
                '>') go_right;;
            esac
            

            【讨论】:

            • @JonathanBondhus:它们是函数名称或您将提供的其他命令或语句的占位符。
            • 还有一件事,我更新了我的脚本,再次看到 [这里](github.com/jbondhus/last-seen/blob/master/last-seen.sh ) 现在它有 ,分配给用户和 .被分配前进。我这样做是为了让用户不必按住 shift 键。到目前为止,我使用 [this](pastebin.com/MiPA5jJD) 作为解析键输入的函数,并且我想更改它以便代替 , for back 和 .对于前进,它使用箭头键。我尝试使用键码 ^[[D 和 ^[[C 作为左右键,例如 '^[[D') 和 '^[[C'),但它仍然不起作用。
            • 是的,不要尝试使用箭头键。要做到这一点,您需要在 Python 脚本或类似的东西中使用受诅咒的模块,而不是尝试使用 Bash 脚本。脚本中的一些 cmets:您可以通过使用 ((var += value))((var++)) 而不是 var=$((var+value)) 来提高可读性并减小代码大小。例如:((total_time -= mo * 2629746))。不需要var=$((0)),只需使用var=0。整数比较:while (( line_number &lt;= total ))。使用$() 而不是反引号进行命令替换。你不需要 awk:wc -l &lt; filename
            • 谢谢,我今年 17 岁,我只写了 3 个月的 bash。
            • 你的开端很好。在我之前的评论中,我说“被诅咒的模块”。那是一个错字。它应该说“诅咒模块”。
            猜你喜欢
            • 2019-09-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-24
            • 2015-11-28
            • 1970-01-01
            • 2010-09-05
            相关资源
            最近更新 更多