Querying Terminal Size in Bash

Today I stumbled upon the Bash variables COLUMNS and LINES. These look neat as, if they actually worked, would mean not having to call tput to get the current size of the terminal.
Bzzzt! Bad luck. These variables are only set when the interactive shell receives a SIGWINCH signal, they are not available to scripts, exactly where they are actually useful. I’m not sure where you’d want to use these in an interactive shell apart from useless gimmicks like having a clock in the corner of your terminal.

I wanted to use these to redraw the screen on a window resize in a new Bash hack that I’m working on. Like so:

1
2
3
4
5
trap on_resize SIGWINCH
function on_resize() {
  do_stuff ${COLUMNS} ${LINES}
  # ...etc.
}

As some sort of masochistic challenge, I’m using only pure Bash, no external commands, in the program I’m working on. So I had to come up with a way to get the dimensions of the terminal screen without resorting to tput.

The terminal escape sequence ESC-[18t makes the terminal respond with its dimensions. This can be used in the following way to query the terminal for its size:

1
2
3
4
function term_size () {
  { echo -ne \\e[18t && read -s -r -d t size; } <> /dev/tty
  echo ${size#*;}
}

For example:

hessch@substrate:~$ term_size
51;72

Update: The function above hangs on the read in some cases. Echoing using read -p is more elegant and does not suffer from hanging. Also especially on some versions of MacOS X echo -ne doesn’t grok \e for escape. The following works better:

1
2
3
4
5
6
function term_size() {
  {
      read -p $'\e[18t' -s -r -d t size }
  } <> /dev/tty
  echo ${size#*;};
}

The following two functions help accessing either the columns or the rows part:

1
2
3
4
5
6
7
function term_cols () {
    s=$(term_size); echo ${s/*;}
}

function term_rows () {
    s=$(term_size); echo ${s/;*}
}

These can be used as default values when the variables COLUMNS and LINES are unset. Like so:

hessch@substrate:~$ echo ${COLUMNS:-$(term_cols)}
72
hessch@substrate:~$ echo ${LINES:-$(term_rows)}
51

That saves some useless terminal I/O when COLUMNS and LINES are available.

Comments