Today, a friend of mine raised the issue of how to get the terminal window size from within a program. He had observed in his system an environment variable, called COLUMNS, which was automatically set to the window's width; that variable even changed when resizing the window.

But then we checked on two more systems (SuSE Linux and NetBSD) and we couldn't find the variable anywhere. I don't know where it's coming from in his system, but this is clearly not the way to go. So, how to do it?

The tty(4) driver has an ioctl for this specific purpose, i.e., to get the window size of an specific terminal. It's called TIOCGWINSZ and has to be called over an open descriptor. (I let to the reader the task to get information about TIOCSWINSZ.)

The next question is, which descriptor? It depends, but, usually, you'll want to get it from the descriptor on which you are going to write the output (STDOUT_FILENO). Let's see a bit of code (slightly based on NetBSD's ps(1)):

struct winsize ws;
unsigned short height, width;

if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 &&
ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == -1 &&
ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) ||
ws.ws_col == 0) {
height = 25;
width = 80;
} else {
height = ws.ws_row;
width = ws.ws_col;
}

Now that we can get the terminal size on purpose, the next question is how to detect if the window size changes due to an external event - e.g., the user resized his terminal window. The only thing you need to do in this case is to catch the SIGWINCH signal (see signal(7)) and act accordingly; i.e., run the code shown above from inside the signal handler (or any other method you prefer to use, like delayed evaluation).

I guess this method is not extremely portable, but I'm sure it'll work on a lot more systems than the COLUMNS trick.