bash scripting: get current text-cursor's on-screen position using ANSI Escape Sequence codes

Kent West - kent.west@{that mail that swore to do no evil}

Thanks to Paulo Scardine for the heart of and explanation of the following code.

Get the text cursor's current position on the screen

get_cursor_pos.sh
#!/usr/bin/env bash

echo -ne "\033[6n"            # The ANSI code ESC[6n asks the terminal for the position.
                              # The terminal puts the answer in the input buffer, in the format ESC[n;mR, where n;m = row;col.
read -s -d\[ garbage          # Read the input buffer up to the "[" ("s"ilently, without echoing what's read). Place this ESC[
                              # into a variable garbage, which we'll henceforth forget about; we don't need it.
                              # This leaves n;mR in the input buffer.
read -s -d ';' row            # Read again, until we get to the character ';', storing the n in the variable row
read -s -d R col              # Read again, until we get to the character 'R', storing the m in the variable col
echo "The (row,col) coords are ($row,$col)"

Printing the ANSI Escape Sequence ESC[6n asks the terminal for the current cursor position, which is returned to the input buffer in the format ESC[n;mR (which may sometimes be represented as ^[[n;mR). The read command reads from the input buffer, usually until a newline is encountered, but the -d <some_char> (delimiter) argument will make read stop at the specified character instead.

An alternative one-liner method to get the text cursor's current position on the screen

get_cursor_pos.sh
#!/usr/bin/env bash

# Magic to read the current cursor position (origin 1,1)
tput u7; read -t1 -srd'[' _; IFS=';' read -t1 -srd'R' row col 

printf "Row: %d\nCol: %d\n\n" $row $col

tput u7 uses the "user7" command of tput to essentially printout the \033[6n ANSI code.

The rest of the one-liner does pretty much the same as the more verbose version, with the _ being an "unused, not-named variable" equivalent to garbage.

Another Alternative Method

This is essentially a mix of the two methods above.

get_cursor_pos.sh
#!/usr/bin/env bash

echo -ne "\033[6n"            # Ask the terminal for the position.
                              # The terminal puts the answer in the input buffer,
                              # in the format ESC[row;col.
read -sd'[' _                 # Read input, silently, until a '[' is reached as a stop-
                              # reading delimiter, storing it in a throwaway "variable".
IFS=';'                       # Change default stop-reading delim from newline to ";".
read -sd'R' row col           # Read rest of input as two fields, up to "R".

printf "Row: %d\nCol: %d\n\n" $row $col

Get the dimensions of the window

If you want to get the dimensions of the current window, just send the cursor to way beyond any expected col/row maximum; the cursor will stop at the screen's maximums, at which point you can then read the position.

get_screen_dims_pos.sh
#!/usr/bin/env bash

echo -ne "\033[10000;10000H"  # Try to move cursor past any expected row/col maximums. Cursor will stop at the maximums.
                              # Then read the maximums.
echo -ne "\033[6n"            # Ask the terminal for the position
                              # The response looks like ESC[n;mR - where n = row, m = col
read -s -d '[' _              # Read the response silently, discarding the first part of the response, leaving n;mR
read -s -d ';' rows           # Read some more, silently, until we get to the character ';', storing what we read in the variable rows
read -s -d R cols             # Read some more, silently, until we get to the character 'R', storing what we read in the variable cols
echo                          # Echo a newline to get the cursor back to the left side of the screen.
printf "The screen dimensions are %dX%d.\n" $rows $cols  # Using 'printf' 'cause I didn't want to bother hunting how to get echo to print without spaces.

An Easier Way than Using ANSI Escape Sequences to get Just the Screen Dimensions

get_cursor_pos.sh
#!/usr/bin/env bash

# Use "tput" to get dimensions
rows=$(tput lines)
cols=$(tput cols)

printf "Rows: %d  -  Cols: %d\n" $rows $cols

Here's an elegant script that is very informative:

get_cursor_pos.sh
#!/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