LINES and COLUMNS environmental variables lost in a script

Because this question is popular, I want to add a newer answer with a bit of additional information.

Often, on modern systems, the $COLUMNS and $LINES variables are not environment variables. The shell sets these values dynamically after each command and we usually cannot access them from non-interactive scripts. Some programs respect these values if we export them, but this behavior isn't standardized or universally supported.

Bash sets these variables in the scope of the process (not the environment) when we enable the checkwinsize option using:

shopt -s checkwinsize 

Many systems enable this option for us in a default or system-wide startup file (/etc/bashrc or similar), so we need to remember that these variables may not always be available. On some systems, such as Cygwin, this option is not enabled for us, so Bash doesn't set $COLUMNS and $LINES unless we execute the line above or add it to our ~/.bashrc.

Portable Approaches

When writing non-interactive scripts, we usually don't want to rely on $LINES and $COLUMNS by default (but we can check these to allow a user to override the terminal size manually if desired).

Instead, the stty and tput utilities provide portable means to determine the terminal size from a script (the commands described below are currently undergoing standardization for POSIX).

As shown in the accepted answer by Puppe, we can use tput to gather the terminal size in a pretty straightforward manner:

lines=$(tput lines)
columns=$(tput cols)

Alternatively, the size query for stty gives us the number of terminal rows and columns in one step (output as the number of lines followed by two spaces followed by the number of columns):

size=$(stty size)  # "40  80" for example 

The stty program usually ships with GNU Coreutils, so we can often find it on systems without tput. I sometimes prefer the stty approach because we invoke one fewer command and subshell (expensive on Cygwin), but it does require that we parse the output into rows and columns, which may be less readable:

lines=${size% *}
columns=${size#* }

Both of the approaches described above work in any POSIX shell.

Non-portable Approaches

If we don't care about portability, Bash supports process substitution to simplify the previous example:

read lines columns < <(stty size) 

...which runs faster than the tput example, but still slower than the first stty implementation, at least on my machine. In practice, the performance impact is probably negligible—choose the approach that works best for the program (or based on which command is available on the target system).

For Bash versions 4.3 and later, we can exploit the checkwinsize option to avoid a dependency on an another program. When we enable this option in a script, Bash will set $LINES and $COLUMNS like it does for an interactive prompt after a child process exits:

#!/bin/bash
shopt -s checkwinsize
cat /dev/null # Refresh LINES and COLUMNS

...like when a subshell exits:

shopt -s checkwinsize
(: Refresh LINES and COLUMNS)

Bash fetches the terminal size after every external command invocation if we enable this option, so we may want to turn it back off after initializing the variables:

shopt -u checkwinsize

If, for some reason, we still want to use $LINES and $COLUMNS from the environment in our scripts, we can configure Bash to export these variables to the environment:

trap 'export LINES COLUMNS' DEBUG

The Bash DEBUG trap executes before each command entered at the prompt, so we can use it to export these variables. By re-exporting them with each command, we ensure that the environment variables remain up-to-date if the terminal size changes. Add this line to .bashrc along with the checkwinsize option shown above. It works fine for personal scripts, but I don't recommend using these variables in any script that will be shared.


You could get the lines and columns from tput:

#!/bin/bash

lines=$(tput lines)
columns=$(tput cols)

echo "Lines: " $lines
echo "Columns: " $columns

eval $( resize )

does that job...(on xterm-based terminal)