84

Consider the following:

me@mine:~$ cat a.sh 
#!/bin/bash
echo "Lines: " $LINES
echo "Columns: " $COLUMNS
me@mine:~$ ./a.sh 
Lines: 
Columns: 
me@mine:~$ echo "Lines: " $LINES
Lines:  52
me@mine:~$ echo "Columns: " $COLUMNS
Columns:  157
me@mine:~$ 

The variables $LINES and $COLUMNS are shell variables, not environmental variables, and thus are not exported to the child process (but they are automatically updated when I resize the xterm window, even when logged in via ssh from a remote location). Is there a way in which I can let my script know the current terminal size?

EDIT: I need this as a workaround do this problem: vi (as well as vim, less, and similar commands) messes up the screen every time I use it. Changing the terminal is not an option, and thus I'm looking for workarounds (scrolling down $LINES lines surely is not the perfect solution, but at least is better than losing the previous screen)

Community
  • 1
  • 1
Davide
  • 17,098
  • 11
  • 52
  • 68
  • I'd guess you can solve your original problem with a "Ctrl-L" command to vi. – ndim Nov 23 '09 at 14:08
  • @ndim: Thanks for the suggestion, but you should write it on the other question (where I would answer you that it doesn't work) – Davide Nov 23 '09 at 15:16
  • Davide, on whim, I scrolled all the way down to the bottom and found Cy's answer, and I'm really glad I did. You may want to consider switching the answer you accepted. It will help a lot of people who find this question. – mr_carrera Jan 27 '18 at 21:51

11 Answers11

95

You could get the lines and columns from tput:

#!/bin/bash

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

echo "Lines: " $lines
echo "Columns: " $columns
heemayl
  • 39,294
  • 7
  • 70
  • 76
Puppe
  • 4,995
  • 26
  • 27
57

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.

Cy Rossignol
  • 16,216
  • 4
  • 57
  • 83
  • 2
    Fantastic answer. Finally understood why systems behave so different...Logged in only to upvote this - and to hint a small error: You reverted lines and columns when matching the `size` output, should be .e.g. `lines=${size#* }`. `size` outputs first lines then columns. – Red Pill Jan 13 '18 at 14:12
  • @RedPill I'm a fool :) checked this out on an actual computer instead of my phone, and I was just misreading the values. In fact, the POSIX proposal defines the format as *`"%1dΔ%1d\n", , `*, so we can rely on the assumption that stty outputs rows first, then columns. I'll update the answer...thanks! – Cy Rossignol Jan 15 '18 at 14:46
  • Manual page og GNU Bash 5.0 says that the `checkwinsize` option is enabled by default. – jarno Nov 10 '21 at 11:36
8
eval $( resize )

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

tanascius
  • 53,078
  • 22
  • 114
  • 136
Anthony
  • 81
  • 1
  • 1
6
kill -s WINCH $$

does set the variables.

ronalchn
  • 12,225
  • 10
  • 51
  • 61
elo
  • 69
  • 1
  • 1
6

For the sake of completion, let me mention that setting the 'checkwinsize' option is exactly what the OP is looking for, but there is a catch. It is by default unset in non-interactive scripts, but you can elect to add the following line at the beginning of any script to enable it :

shopt -s checkwinsize

Unfortunately, the LINES and COLUMNS variables are not set immediately upon setting the option (at least the last time I tried). Instead, you need to force Bash to wait for a subshell to complete, at which point it will set those variables. The full Bash-only solution to this problem is thus to start your script with the following line :

shopt -s checkwinsize; (:;:)

You can then use the LINES and COLUMNS variables to your heart's content, and they will be reset to the correct values each time the terminal is resized, without needing to call any external utilities.

Marc Coiffier
  • 194
  • 1
  • 5
  • 1
    This seems to work only with new versions of bash. It does not work on older version of bash (neither 4.2.46 on centos7 nor 4.2.37 on debian7). However, the tput method (see Puppe's answer) seems to work everywhere. – Ján Lalinský Oct 29 '18 at 15:31
  • 1
    @JánLalinský no it does/did not, see [here](https://stackoverflow.com/q/21763397/4414935) – jarno Nov 10 '21 at 08:15
5

Running help export might help?

me@mine:~$ cat a.sh 
#!/bin/bash
echo "Lines: " $LINES
echo "Columns: " $COLUMNS
me@mine:~$ ./a.sh 
Lines: 
Columns: 
me@mine:~$ echo "Lines: " $LINES
Lines:  52
me@mine:~$ echo "Columns: " $COLUMNS
Columns:  157
me@mine:~$ export LINES COLUMNS
me@mine:~$ ./a.sh 
Lines:  52
Columns:  157
me@mine:~$ 
SwordFish
  • 61
  • 1
  • 1
  • 1
    Not sure why this was downvoted. I have `export LINES=$LINES` and `export COLUMNS=$COLUMNS` in my bashrc, and that works correctly for me. You don't _have_ to mess with tput. – tandrewnichols Jan 02 '17 at 15:19
4

Have you tried making your shebang say:

#!/bin/bash -i
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • Also, see my answer to the question you referenced. Setting the `t_ti` variable to null may help with `vim`. http://stackoverflow.com/questions/630519/can-you-make-vi-advance-the-screen-when-opened/1780630#1780630 – Dennis Williamson Nov 23 '09 at 01:00
  • Unfortunately, `#!/bin/bash -i` does not make **any** difference neither in AIX nor in linux – Davide Nov 23 '09 at 01:51
  • 1
    I get blank output from your script without the `-i` and correct numbers with it. This is on Ubuntu (LINES and COLUMNS are not exported). I found that on Cygwin, I had to export the two variables (you could do this in ~/.bashrc) in order to get it to work and the `-i` wasn't needed. However, I had to do `kill -SIGWINCH $$` at the Bash prompt to get the values to update if I resized the window (for Cygwin). – Dennis Williamson Nov 23 '09 at 02:34
  • Ubuntu what? On Hardy Haron, `-i` does not make any difference: blank output with or without it (anyway, I need this on AIX, not Ubuntu) – Davide Nov 23 '09 at 15:12
  • It's a Bash thing. It shouldn't be an OS thing (unless SIGWINCH isn't getting sent). I've tested this on Bash 3.2 and 4.0. – Dennis Williamson Nov 23 '09 at 19:43
  • 1
    Bad idea, since for bash, the -i option means the shell is interactive. So this has side effects like running your ~/.bashrc file. – MarkHu Jun 21 '12 at 20:30
2

$LINES and $COLUMNS in bash is just a shell-y wrapper around the TTY ioctls giving you the size of the TTY and the signals sent by the terminal every time that size changes.

You could write a program in some other language which calls those ioctls directly to get to the TTY dimensions, and then use that program.

EDIT: Well, turns out that program already exists, and is called tput. Vote up Puppe's tput based answer.

Community
  • 1
  • 1
ndim
  • 35,870
  • 12
  • 47
  • 57
2
#!/bin/bash -i

-i works now with bash 4.2.10(1)-release on Ubuntu 11.10.

$ cat show_dimensions.sh 
#!/bin/bash -i
printf "COLUMNS = %d\n" $COLUMNS
printf "LINES = %d\n" $LINES

$ ./show_dimensions.sh 
COLUMNS = 150
LINES = 101

$ bash --version
GNU bash, version 4.2.10(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

The numbers do change with a window resize; a trap reveals the script is getting a SIGWINCH.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Seganku
  • 149
  • 5
1

Why not use enviroment variables on exec command like this:

docker exec -ti -e LINES=$LINES -e COLUMNS=$COLUMNS  container /bin/bash
Classsic
  • 165
  • 1
  • 5
-1

My experince that you should start the script by the '. script_to_run' form, instead of the 'scritp_to_run'. A simple check as follows:

'(( ${#COLUMNS} )) || { echo "Try start like '. name'" ; return 1 ; }
apexik
  • 79
  • 4
  • 2
    This way of execution is called sourcing and `.` command is called `source`. Not all scripts are written to support this way of execution. E.g. when the script calls `exit`, the whole shell that sourced the script exits, not only the script. See [What is the difference between `sh` and `source`?](https://stackoverflow.com/a/13786536/2157640). – Palec Jan 28 '18 at 14:04