395

As a simple example, I want to write a CLI script which can print = across the entire width of the terminal window.

#!/usr/bin/env php
<?php
echo str_repeat('=', ???);

or

#!/usr/bin/env python
print '=' * ???

or

#!/usr/bin/env bash
x=0
while [ $x -lt ??? ]; do echo -n '='; let x=$x+1 done; echo
Daniel Böhmer
  • 14,463
  • 5
  • 36
  • 46
too much php
  • 88,666
  • 34
  • 128
  • 138

10 Answers10

732
  • tput cols tells you the number of columns.
  • tput lines tells you the number of rows.
Flow
  • 23,572
  • 15
  • 99
  • 156
TonyUser
  • 7,630
  • 1
  • 18
  • 8
  • 18
    `echo -e "lines\ncols"|tput -S` to get both the lines and cols see: http://linux.about.com/library/cmd/blcmdl1_tput.htm – nickl- Jan 26 '13 at 03:49
  • 6
    `tput` is a great command with [lots of commands](http://stackoverflow.com/a/20983251/24874) for reading the state of the terminal, controlling the cursor and text properties, and so on. – Drew Noakes Jan 07 '14 at 22:27
  • 3
    Handy alias, for example: `alias dim="echo $(tput cols)x$(tput lines)"`, which might result in `80x50`. – bishop Apr 10 '14 at 13:40
  • 2
    This Q&A *probably* belongs on either the unix or superuser SE sites. – mydoghasworms Aug 18 '14 at 05:32
  • One problem with tput: there is no --quiet option to silence the "unknown terminal" or "No value for $TERM" error messages and 2>/dev/null breaks it too. stty size seem to do better. – MarcH Jan 29 '15 at 19:25
  • 8
    @bishop the alias command you provided gets evaluated when the shell gets sourced. You need to use single quotes for the alias command. Like so: `alias dim='echo Terminal Dimensions: $(tput cols) columns x $(tput lines) rows'` – brandonsimpkins Jul 27 '18 at 13:49
  • `tput` as defined by POSIX doesn't support `cols` or `lines`. This is a non-standardized extension. This solution is not reliable. – Mecki Oct 01 '20 at 21:33
  • requires ncurses, and requires calling two commands, while you can `stty size` it away – ZJR May 04 '22 at 02:38
133

In bash, the $LINES and $COLUMNS environmental variables should be able to do the trick. The will be set automatically upon any change in the terminal size. (i.e. the SIGWINCH signal)

David Dean
  • 7,435
  • 6
  • 33
  • 41
  • 25
    However, these environment variables are only available to bash, and not to any programs that run inside bash (like perl, python, ruby). – Br.Bill Feb 29 '12 at 23:20
  • 11
    That does not work in anything but the interactive bash session (if you run the script it is not interactive any longer). The only place you can use it in a script is the prompt_command in bash. – Doncho Gunchev Feb 28 '14 at 07:59
  • 5
    Actually, it does work in non-interactive scripts, if you set the `checkwinsize` option. For example, this non-interactive script will print the dimensions of the terminal on which it is run : `shopt -s checkwinsize; (:); echo $LINES $COLUMNS` (the `checkwinsize` option only initializes the variables after waiting for a subshell to finish, which is why we need the `(:)` statement) – user3340662 Aug 10 '17 at 11:52
  • `$LINES` and `$COLUMNS` are updated after `SIGWINCH` is sent, actually after any interactive command execution. If you try to update `PS1` with `trap SIGWINCH` you can't use `$LINES` and `$COLUMNS`, they keep old values (( – gavenkoa Dec 30 '18 at 10:45
  • 2
    `LINES` and `COLUMNS` are only set as *shell variables* by bash. Bash will not set them as *environment variables*, unless you export these shell variables. – Markus Kuhn Feb 01 '20 at 15:29
  • @Br.Bill `COLUMNS` and `LINES` are defined by POSIX, they have nothing to do with bash and nowhere is defined, that its the task of the shell to set them. POSIX says that the user can set them to desired values: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08 – Mecki Oct 01 '20 at 21:43
107

And there's stty, see stty: Print or change terminal characteristics, more specifically Special settings

$ stty size
60 120 # <= sample output

# To read into variables, in bash
$ read -r rows cols < <(stty size)
$ echo "rows: $rows, cols: $cols"
rows: 60, cols: 120

It will print the number of rows and columns, or height and width, respectively.

Or you can use either cut or awk to extract the part you want.

That's stty size | cut -d" " -f1 for the height/lines and stty size | cut -d" " -f2 for the width/columns

ryenus
  • 15,711
  • 5
  • 56
  • 63
  • This style cannot work with PIPE, suggest use tput style. – liuyang1 Jul 14 '15 at 12:34
  • 9
    the problem with tput is that it is not always available while stty is available in every tty. thanks for that info! – iRaS Sep 23 '18 at 07:58
  • 1
    `stty` is not from coreutils. stty is POSIX standard and thus pretty much available everywhere, also on BSD systems that definitely won't have coreutil Coreutils just implements the majority of the POSIX terminal standard. – Mecki Oct 01 '20 at 21:09
22
yes = | head -n$(($(tput lines) * $COLUMNS)) | tr -d '\n'
pixelbeat
  • 30,615
  • 9
  • 51
  • 60
12

On POSIX, ultimately you want to be invoking the TIOCGWINSZ (Get WINdow SiZe) ioctl() call. Most languages ought to have some sort of wrapper for that. E.g in Perl you can use Term::Size:

use Term::Size qw( chars );

my ( $columns, $rows ) = chars \*STDOUT;
LeoNerd
  • 8,344
  • 1
  • 29
  • 36
  • 2
    Thanks for this – led me in the right direction. Elixir: `:io.columns` Erlang: `io:columns().` http://erlang.org/doc/man/io.html#columns-0 – Henrik N Oct 01 '15 at 20:59
  • 2
    There's no `TIOCGWINSZ` in the POSIX standard and `ioctl()` is only defined for the obsolescent STREAMS feature. – osvein Oct 02 '17 at 20:41
12

To do this in Windows CLI environment, the best way I can find is to use the mode command and parse the output.

function getTerminalSizeOnWindows() {
  $output = array();
  $size = array('width'=>0,'height'=>0);
  exec('mode',$output);
  foreach($output as $line) {
    $matches = array();
    $w = preg_match('/^\s*columns\:?\s*(\d+)\s*$/i',$line,$matches);
    if($w) {
      $size['width'] = intval($matches[1]);
    } else {
      $h = preg_match('/^\s*lines\:?\s*(\d+)\s*$/i',$line,$matches);
      if($h) {
        $size['height'] = intval($matches[1]);
      }
    }
    if($size['width'] AND $size['height']) {
      break;
    }
  }
  return $size;
}

I hope it's useful!

NOTE: The height returned is the number of lines in the buffer, it is not the number of lines that are visible within the window. Any better options out there?

lyceus
  • 149
  • 1
  • 3
  • 4
    Note a problem with this: the output of this command is locale-specific. In other words, this will not work as-is on another Windows locale. This is what I get on Windows 7: http://i.imgur.com/Wrr7sWY.png – Camilo Martin Jan 17 '14 at 15:27
  • Added an answer with a solution to that. +1 though! – Camilo Martin Jan 17 '14 at 15:53
4

As I mentioned in lyceus answer, his code will fail on non-English locale Windows because then the output of mode may not contain the substrings "columns" or "lines":

                                         mode command output

You can find the correct substring without looking for text:

 preg_match('/---+(\n[^|]+?){2}(?<cols>\d+)/', `mode`, $matches);
 $cols = $matches['cols'];

Note that I'm not even bothering with lines because it's unreliable (and I actually don't care about them).

Edit: According to comments about Windows 8 (oh you...), I think this may be more reliable:

 preg_match('/CON.*:(\n[^|]+?){3}(?<cols>\d+)/', `mode`, $matches);
 $cols = $matches['cols'];

Do test it out though, because I didn't test it.

Camilo Martin
  • 37,236
  • 20
  • 111
  • 154
  • Your method doesn't work in Win8. I get more than one `---` line. http://i.imgur.com/4x02dqT.png – mpen Aug 13 '14 at 01:01
  • @Mark Well, great, that is just BEAUTIFUL. Thank you Windows. <3 (on a more relevant note: I'll see into how to fix that... when Windows 9 comes out :P). – Camilo Martin Aug 13 '14 at 01:11
  • This is the way I do it: `$mode = \`mode\`; list($rows, $cols) = array_slice(preg_split('/\n/', substr($mode, strpos($mode, 'CON:'))), 2, 2);`. And then I just replace everything but numbers. – Aleksandr Makov Sep 16 '14 at 14:48
  • @AleksandrMakov I wonder what happens if there's languages with order like `CON device status:`? Maybe matching something like `CON.*:` would work better. – Camilo Martin Sep 16 '14 at 19:37
  • @CamiloMartin, the thing is, that "CON:" string remains the same. I checked ru locale, I see the same structure. Lines are on the on the 2nd row after "CON:" string. Columns are on the 3rd. – Aleksandr Makov Sep 17 '14 at 05:57
  • @AleksandrMakov How do you know it remains the same on all languages? I can imagine the phrasing where `CON` can be the last word may not be usable in *some* languages. What I think is certain is that the word `CON` is in a line that ends with `:`. But even that may not be true :P I just wish they had kept all of command line in English. – Camilo Martin Sep 17 '14 at 20:03
  • Well, I'm not providing a universal solution after all, just point out an idea how I do it. – Aleksandr Makov Sep 18 '14 at 05:19
  • That looks like it works, yeah. Can you explain why you're searching for anything except a `|` though? – mpen Sep 18 '14 at 20:40
  • 1
    @Mark I was actually questioning myself that exact thing. Why the heck did I do that? In doubt, I just assumed there was some reason and went with it, lol. – Camilo Martin Sep 19 '14 at 02:15
  • `mode` is slow. Using `mode con /status` is faster, as it excludes all the modems and just gets the console status. Still not lighting fast, but much better than single `mode`. Also, if you use `mode con /status`, your first RE works as well, as it only returns the console status. – Christos Lytras Feb 20 '19 at 15:10
4

Inspired by @pixelbeat's answer, here's a horizontal bar brought to existence by tput, slight misuse of printf padding/filling and tr

printf "%0$(tput cols)d" 0|tr '0' '='
huoneusto
  • 1,164
  • 1
  • 8
  • 19
3

Getting the window width

This shell code makes a global variable $TERM_SIZE track the size of the terminal window:

set_term_size() {
    TERM_SIZE="$(stty size 2>/dev/null)" && [ "$TERM_SIZE" ] ||
        TERM_SIZE='25 80'
}
trap set_term_size WINCH
set_term_size

It tries stty size before falling back to assuming that the terminal is 25 lines high and 80 characters wide. POSIX does not mandate the size operand for stty`, so the fallback is needed.

You can then access the columsn argument by using the shell's limited string substitution capabilities:

echo "${TERM_SIZE% *}" # Prints the terminal's height.
echo "${TERM_SIZE#* }" # Prints the terminal's width.

Of course, the scripting language you use likely offers a library that takes care of that for you -- and you should use it.

Printing a line

Once you know the width of the terminal, printing a horizontal line is easy, for example, by abusing printf's string padding:

printf '%*s\n' "${TERM_SIZE#* }" '' | 
tr ' ' -

The first line tells printf to print as many spaces as there are columns (by abusing string paddin) to a pipe. Note, POSIX does not mention the * syntax, so this may not be as portable as the code above.

The second line tells tr to read from that pipe and replace every space with a hypen.

1

There are some cases where your rows/LINES and columns do not match the actual size of the "terminal" being used. Perhaps you may not have a "tput" or "stty" available.

Here is a bash function you can use to visually check the size. This will work up to 140 columns x 80 rows. You can adjust the maximums as needed.

function term_size
{
    local i=0 digits='' tens_fmt='' tens_args=()
    for i in {80..8}
    do
        echo $i $(( i - 2 ))
    done
    echo "If columns below wrap, LINES is first number in highest line above,"
    echo "If truncated, LINES is second number."
    for i in {1..14}
    do
        digits="${digits}1234567890"
        tens_fmt="${tens_fmt}%10d"
        tens_args=("${tens_args[@]}" $i)
    done
    printf "$tens_fmt\n" "${tens_args[@]}"
    echo "$digits"
}
pourhaus
  • 566
  • 6
  • 9
  • This is good, if your `stty` won't give you **real size!** Very good @pourhaus ! I just changed the number in line `for i in {1..14}` for checking the number of columns from 14 to 25. – timmotej Mar 30 '22 at 07:40