9

This might sound pretty weird, but I know how to set the vertical cursor position in Bash like this:

echo -e "\e[12H"

This moves the cursor to the 12th line (starting with 1).

So how do I get the cursor position (line number) using linux bash? It would be helpful if I could simply store this value in a variable so I can calculate with it.

EDIT:

This is the error I get:

$ sh rowcol.sh
-en
    read: 9: Illegal option -d
                              test.sh: 12: Bad substitution
  • See also an example script (not the simplest because that one had additional constraints). – Gilles Aug 27 '13 at 7:32
5

I was able to use some of the examples from the same article on SO, titled: How to get the cursor position in bash?. I'm posting this here just to show that they work and that the contents of solutions is actually on U&L as well.

Bash solutions

From inside a script

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

echo "(row,col): $row,$col"

NOTE: I changed the output slightly!

Example

$ ./rowcol.bash 
(row,col): 43,0
$ clear
$ ./rowcol.bash 
(row,col): 1,0

Interactive shell

This command chain worked for getting the row and column positions of the cursor:

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

Example

$ echo -en "\E[6n";read -sdR CURPOS; CURPOS=${CURPOS#*[};echo "${CURPOS}"
13;1
$ clear
$ echo -en "\E[6n";read -sdR CURPOS; CURPOS=${CURPOS#*[};echo "${CURPOS}"
2;1

NOTE: This method doesn't appear to be usable from any type of script. Even simple commands in an interactive terminal didn't work for me. For example:

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

just hangs indefinitely.

dash/sh solutions

From inside a script

This solution is for Ubuntu/Debian systems that come stock with dash, which is POSIX compliant. Because of this, the read command doesn't support the -d switch among other differences.

To get around this there is this solution which uses a sleep 1 in place of the -d switch. This isn't ideal but offers at least a working solution.

#!/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 "(row,col): $col,$row"

Example

$ ./rowcol.sh 
(row,col): 0,24
$ clear
$ ./rowcol.sh 
(row,col): 0,1

Interactive shell

I couldn't find a workable solution that worked for just sh in an interactive shell.

  • This seems to only work with bash. And not with sh. I personally prefer sh. So how could I use this with sh? – BrainStone Aug 27 '13 at 1:43
  • 1
    @BrainStone - let me research and see if I can't find a way. – slm Aug 27 '13 at 1:43
  • sh rowcol.sh. It doesn't matter what you put in the first line (#!/bin/bash or #!/bin/sh) or what ending the file has! – BrainStone Aug 27 '13 at 1:47
  • @BrainStone - but I think sh is just a compatibility mode of bash. When I do that (sh rowcol.bash) it works, does it not work for you then? – slm Aug 27 '13 at 1:58
  • 1
    @BrainStone - you could make an alias, alias sh=bash? – slm Aug 27 '13 at 2:56
15

Using the -p option instead of echo I found solved the hanging problem in a script. Tested with GNU bash, version 3.00.16(1)-release (x86_64-redhat-linux-gnu).

IFS=';' read -sdR -p $'\E[6n' ROW COL;echo "${ROW#*[}"

works interactively or in a script:

#!/usr/bin/env bash
function pos
{
    local CURPOS
    read -sdR -p $'\E[6n' CURPOS
    CURPOS=${CURPOS#*[} # Strip decoration characters <ESC>[
    echo "${CURPOS}"    # Return position in "row;col" format
}
function row
{
    local COL
    local ROW
    IFS=';' read -sdR -p $'\E[6n' ROW COL
    echo "${ROW#*[}"
}
function col
{
    local COL
    local ROW
    IFS=';' read -sdR -p $'\E[6n' ROW COL
    echo "${COL}"
}
tput sc         # Save cursor position
tput cup 5 10   # Move to row 6 col 11
POS1=$(pos)     # Get the cursor position
ROW1=$(row)
COL1=$(col)
tput cup 25 15  # Move to row 25 col 15
POS2=$(pos)     # Get the cursor position
ROW2=$(row)
COL2=$(col)
tput rc # Restore cursor position
echo $BASH_VERSION
echo $POS1 $ROW1 $COL1
echo $POS2 $ROW2 $COL2

Outputs:

$./cursor.sh
3.00.16(1)-release
6;11 6 11
26;16 26 16
  • This works very well. Sadly however, it doesn't work in background processes, and as the screen scrolls, the line under the saved column changes. – meonlol Jul 4 '18 at 15:51
4

You can get the cursor position via ANSI CSI DSR (Device Status Report): \e[6n. Note it returns it in a format similar to ANSI CSR CUP (Cursor Position) that you mention in your question, however it follows the form \e[n;mR (where n is the row and m the column).

More details of ANSI escape codes on wikipedia.

For getting hold of the value into a variable, this was answered on StackOverflow.

As mentioned in a previous answer/comment (and detailed in the wikipedia article), these codes are not always portable (from terminal to terminal and OS to OS). I still think this is better handled with termcap/curses ;)

  • And how could I store this in a variable? – BrainStone Aug 26 '13 at 23:50
  • I can't get it work. I always get problems with echo -e, echo -en and read .... This only happens when the code is present in the file! I don't really understand this! – BrainStone Aug 27 '13 at 0:54
  • It seems like I broke some setting. echo -e worked before but now it doesn't! What might have caused this and how do I restore it? – BrainStone Aug 27 '13 at 1:11
2

With POSIX sh syntax:

if [ -t 0 ] && [ -t 1 ]; then
  old_settings=$(stty -g) || exit
  stty -icanon -echo min 0 time 3 || exit
  printf '\033[6n'
  pos=$(dd count=1 2> /dev/null)
  pos=${pos%R*}
  pos=${pos##*\[}
  x=${pos##*;} y=${pos%%;*}
  stty "$old_settings"
fi

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.