【问题标题】:How to get the cursor position in bash?如何在bash中获取光标位置?
【发布时间】:2011-02-04 05:24:57
【问题描述】:

在 bash 脚本中,我想在变量中获取光标列。看起来使用 ANSI 转义码 {ESC}[6n 是获得它的唯一方法,例如以下方式:

# Query the cursor position
echo -en '\033[6n'

# Read it to a variable
read -d R CURCOL

# Extract the column from the variable
CURCOL="${CURCOL##*;}"

# We have the column in the variable
echo $CURCOL

不幸的是,这会将字符打印到标准输出,我想默默地做。此外,这不是很便携...

有没有一种纯 bash 的方式来实现这一点?

【问题讨论】:

    标签: bash text-cursor


    【解决方案1】:

    你必须使用肮脏的伎俩:

    #!/bin/bash
    # based on a script from http://invisible-island.net/xterm/xterm.faq.html
    exec < /dev/tty
    oldstty=$(stty -g)
    stty raw -echo min 0
    # on my system, the following line can be replaced by the line below it
    echo -en "\033[6n" > /dev/tty
    # tput u7 > /dev/tty    # when TERM=xterm (and relatives)
    IFS=';' read -r -d R -a pos
    stty $oldstty
    # change from one-based to zero based so they work with: tput cup $row $col
    row=$((${pos[0]:2} - 1))    # strip off the esc-[
    col=$((${pos[1]} - 1))
    

    【讨论】:

    • @DennisWilliamson 这里提出了一个更好的解决方案:unix.stackexchange.com/a/183121/106138
    • 我在PROMPT_COMMAND 中使用此命令来查找水平光标位置,这样我就可以在反向视频中打印%,然后每当最后一个命令的输出不输出时换行不要以换行符结束。但是,当我将多行命令粘贴到我的 shell 中时,我认为 read 或其他所需的命令正在吞噬文本。任何已知的解决方案?
    • 您需要surround non-printing sequences\[\],因此如果您不这样做,它们的长度不会计入提示的长度,如记录的herestty 应该保护read,但我不确定。或者,如果问题源于exec 行,您可能需要保存并恢复标准输入。您也可以尝试在上面的评论中链接到的技术。
    • +1:这个答案现在链接到Bash - Clearing the last output correctly
    • 有没有像上面@onlynone 提到的那样吞下多行命令的PROMPT_COMMAND 解决方案?我尝试了其他链接选项的多种变体,即使仅使用 $ 没有任何非打印字符的简单提示也无济于事。
    【解决方案2】:

    您可以告诉read 使用-s 标志静默工作:

    echo -en "\E[6n"
    read -sdR CURPOS
    CURPOS=${CURPOS#*[}
    

    然后 CURPOS 等于 21;3

    【讨论】:

    • 对我来说,这在交互式 shell 中有效,但在脚本中无效
    • 感谢您,我不得不在 Makefile 中填写该行的其余部分,并在函数中结束:echo "\033[6n\c"; read -sdR CURPOS; COLS_LEFT="$$(expr $$TERM_COLS - $$(echo $${CURPOS} | cut -d';' -f 2;) + 1)";
    • echo -en "\033[6n"; sleep 1; read -sdR 这个方法不正确。您必须在 echo 命令之前禁用 lflag ECHO。
    【解决方案3】:

    如果其他人正在寻找这个,我在这里遇到了另一个解决方案: https://github.com/dylanaraps/pure-bash-bible#get-the-current-cursor-position

    以下是带有 cmets 的稍作修改的版本。

    #!/usr/bin/env bash
    #
    # curpos -- demonstrate a method for fetching the cursor position in bash
    #           modified version of https://github.com/dylanaraps/pure-bash-bible#get-the-current-cursor-position
    # 
    #========================================================================================
    #-  
    #-  THE METHOD
    #-  
    #-  IFS='[;' read -p $'\e[6n' -d R -a pos -rs || echo "failed with error: $? ; ${pos[*]}"
    #-  
    #-  THE BREAKDOWN
    #-  
    #-  $'\e[6n'                  # escape code, {ESC}[6n; 
    #-  
    #-    This is the escape code that queries the cursor postion. see XTerm Control Sequences (1)
    #-  
    #-    same as:
    #-    $ echo -en '\033[6n'
    #-    $ 6;1R                  # '^[[6;1R' with nonprintable characters
    #-  
    #-  read -p $'\e[6n'          # read [-p prompt]
    #-  
    #-    Passes the escape code via the prompt flag on the read command.
    #-  
    #-  IFS='[;'                  # characters used as word delimiter by read
    #-  
    #-    '^[[6;1R' is split into array ( '^[' '6' '1' )
    #-    Note: the first element is a nonprintable character
    #-  
    #-  -d R                      # [-d delim]
    #-  
    #-    Tell read to stop at the R character instead of the default newline.
    #-    See also help read.
    #-  
    #-  -a pos                    # [-a array]
    #-  
    #-    Store the results in an array named pos.
    #-    Alternately you can specify variable names with positions: <NONPRINTALBE> <ROW> <COL> <NONPRINTALBE> 
    #-    Or leave it blank to have all results stored in the string REPLY
    #-  
    #- -rs                        # raw, silent
    #-  
    #-    -r raw input, disable backslash escape
    #-    -s silent mode
    #-  
    #- || echo "failed with error: $? ; ${pos[*]}"
    #-  
    #-     error handling
    #-  
    #-  ---
    #-  (1) XTerm Control Sequences
    #-      http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Functions-using-CSI-_-ordered-by-the-final-character_s_
    #========================================================================================
    #-
    #- CAVEATS
    #-
    #- - if this is run inside of a loop also using read, it may cause trouble. 
    #-   to avoid this, use read -u 9 in your while loop. See safe-find.sh (*)
    #-
    #-
    #-  ---
    #-  (2) safe-find.sh by l0b0
    #-      https://github.com/l0b0/tilde/blob/master/examples/safe-find.sh
    #=========================================================================================
    
    
    #================================================================
    # fetch_cursor_position: returns the users cursor position
    #                        at the time the function was called
    # output "<row>:<col>"
    #================================================================
    fetch_cursor_position() {
      local pos
    
      IFS='[;' read -p $'\e[6n' -d R -a pos -rs || echo "failed with error: $? ; ${pos[*]}"
      echo "${pos[1]}:${pos[2]}"
    }
    
    #----------------------------------------------------------------------
    # print ten lines of random widths then fetch the cursor position
    #----------------------------------------------------------------------
    # 
    
    MAX=$(( $(tput cols) - 15 ))
    
    for i in {1..10}; do 
      cols=$(( $RANDOM % $MAX ))
      printf "%${cols}s"  | tr " " "="
      echo " $(fetch_cursor_position)"
    done
    

    【讨论】:

    • read -s 选项的使用很棒,让转义序列消失
    • @Alissa H — 这很棒,虽然我花了很长时间才弄清楚发生了什么!比使用数组稍微简单一点,你可以使用这个:IFS='[;' read -sd R -p $'\e[6n' _ ROW COLUMN。我不明白-r 的意思,没有它我的脚本也能正常工作,所以我省略了它;如果有错误请告诉我。
    【解决方案4】:

    您可以通过以下方式获取 bash 中的光标位置:

    xdotool getmouselocation
    
    $ x:542 y:321 screen:0 window:12345678
    

    您也可以通过以下方式在终端上轻松测试:

    实时变体 1:

    watch -ptn 0 "xdotool getmouselocation"
    

    实时变体 2:

    while true; do xdotool getmouselocation; sleep 0.2; clear; done
    

    【讨论】:

      【解决方案5】:

      出于可移植性的考虑,我尝试制作一个兼容 POSIX 的普通版本,可以在 dash 等 shell 中运行:

      #!/bin/sh
      
      exec < /dev/tty
      oldstty=$(stty -g)
      stty raw -echo min 0
      tput u7 > /dev/tty
      sleep 1
      IFS=';' read -r row col
      stty $oldstty
      
      row=$(expr $(expr substr $row 3 99) - 1)        # Strip leading escape off
      col=$(expr ${col%R} - 1)                        # Strip trailing 'R' off
      
      echo $col,$row
      

      ...但我似乎找不到 bash 的“read -d”的可行替代方案。没有睡眠,脚本会完全错过返回输出...

      【讨论】:

        【解决方案6】:

        我的(两个)版本相同...

        作为一个函数,设置特定变量,使用ncurses的用户定义命令:

        getCPos () { 
            local v=() t=$(stty -g)
            stty -echo
            tput u7
            IFS='[;' read -rd R -a v
            stty $t
            CPos=(${v[@]:1})
        }
        

        比现在:

        getCPos 
        echo $CPos
        21
        echo ${CPos[1]}
        1
        echo ${CPos[@]}
        21 1
        
        declare -p CPos
        declare -a CPos=([0]="48" [1]="1")
        

        注意:我使用 ncurses 命令:tput u7 at line #4,希望这将比 便携通过命令使用 VT220 字符串:printf "\033[6n"... 不确定:无论如何这将适用于其中任何一个:

        getCPos () { 
            local v=() t=$(stty -g)
            stty -echo
            printf "\033[6n"
            IFS='[;' read -ra v -d R
            stty $t
            CPos=(${v[@]:1})
        }
        

        VT220 兼容 TERM 下工作时完全相同。

        更多信息

        你可能会在那里找到一些文档:

        VT220 Programmer Reference Manual - Chapter 4

        4.17.2 设备状态报告 (DSR)

        ...

        Host to VT220 (Req 4 cur pos)  CSI 6 n       "Please report your cursor position using a CPR (not DSR) control sequence."
          
        VT220 to host (CPR response)   CSI Pv; Ph R  "My cursor is positioned at _____ (Pv); _____ (Ph)."
                                                      Pv =  vertical position (row)
                                                      Ph =  horizontal position (column)
        

        【讨论】:

        • 没用。此外,最后一个函数挂在tmux-256color
        • 刚刚在 gnome-terminal(256 色)下的 tmux 上进行了测试,效果很好!你用的是什么终端?
        【解决方案7】:

        tput 命令是您需要使用的。简单,快速,没有输出到屏幕。

        #!/bin/bash
        col=`tput col`;
        line=`tput line`;
        

        【讨论】:

        • 您使用的操作系统和版本适合您吗?在我的系统上,没有名为“col”和“line”的 terminfo 功能。但是,复数形式“cols”和“lines”是存在的,但它们返回的是列数和行数,而不是当前光标位置。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多