24

I'm encountering an issue where I am trying to get the size of a terminal by using scripts. Normally I would use the command tput cols inside the console, however I want to be able to accomplish this feature by strictly using scripts.

As of now I am able to detect the running console and get its file path. However I'm struggling to use this information to get the console's width. I've attempted using the command tput, but I'm fairly new to Linux/scripts so therefore don't really know what to do.

The reason for doing this is I want to be able to setup a cron entry that notifies the console of its width/columns every so often.

This is my code so far:

tty.sh

#!/bin/bash

#Get PID of terminal
#terminal.txt holds most recent PID of console in use
value=$(</home/test/Documents/terminal.txt)

#Get tty using the PID from terminal.txt
TERMINAL="$(ps h -p $value -o tty)"
echo $TERMINAL

#Use tty to get full filepath for terminal in use
TERMINALPATH=/dev/$TERMINAL
echo $TERMINALPATH

COLUMNS=$(/home/test/Documents/get_columns.sh)
echo $COLUMNS

get_columns.sh

#!/usr/bin/env bash
echo $(/usr/bin/tput cols)

The normal output of TERMINAL & TERMINALPATH are pts/terminalnumber and /dev/pts/terminalnumber, for example pts/0 & /dev/pts/0

sirgeezer21
  • 343
  • 1
  • 2
  • 7
  • 1
    https://unix.stackexchange.com/questions/16578/resizable-serial-console-window might help you. – phk Jul 29 '16 at 11:54
  • @phk I don't think that helps. The issue there is how to tell the tty driver of actual values for columns/lines. Here it is to determine them from the tty driver. – Chris Davies Jul 29 '16 at 13:17
  • I didn't think cron jobs had controlling terminals. – TMN Jul 29 '16 at 16:08

4 Answers4

24

The tput command is an excellent tool, but unfortunately it can't retrieve the actual settings for an arbitrarily selected terminal.

The reason for this is that it reads stdout for the terminal characteristics, and this is also where it writes its answer. So the moment you try to capture the output of tput cols you have also removed the source of its information.

Fortunately, stty reads stdin rather than stdout for its determination of the terminal characteristics, so this is how you can retrieve the size information you need:

terminal=/dev/pts/1
columns=$(stty -a <"$terminal" | grep -Po '(?<=columns )\d+')
rows=$(stty -a <"$terminal" | grep -Po '(?<=rows )\d+')

By the way, it's unnecessarily cumbersome to write this as echo $(/usr/bin/tput cols).

For any construct echo $(some_command) you are running some_command and capturing its output, which you then pass to echo to output. In almost every situation you can imagine you might as well have just run some_command and let it deliver its output directly. It's more efficient and also easier to read.

Aaron V
  • 103
Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • Which implementation/version of tput/nurses? Mine (ncurses 6.0.20160625) does the TIOCGWINSZ on stderr if it can't do it on stdout. cols=$(tput cols) or cols=$(tput cols 2<> /dev/ttyx) works just fine. – Stéphane Chazelas Jul 29 '16 at 13:44
  • @StéphaneChazelas I've got 5.9+20140913-1+b1 originally installed from Debian "sid". Just looking for a newer version now. – Chris Davies Jul 29 '16 at 13:49
  • 1
    Works as well with ncurses 5.7.20100313 here. Are you positive cols=$(tput cols 2<> /dev/tty1) doesn't work for you? – Stéphane Chazelas Jul 29 '16 at 14:03
  • @StéphaneChazelas fascinating. You're right: if I move stdout away from a terminal, tput cols does read from stderr. I now need to work out how to rewrite my answer... – Chris Davies Jul 29 '16 at 15:13
  • 3
    I'd use stty size <"$terminal" | read rows columns instead of trying to parse stty -a – Random832 Jul 30 '16 at 07:17
  • 1
    @Random832 only this read r c < <(stty size) worked here, but thx :) – Aquarius Power Jan 04 '18 at 23:23
  • @Random832 you can't pipe into read and expect to use the variable it sets. Try it some time. – Chris Davies Mar 19 '18 at 19:05
  • stty -a <"/dev/pts/1" works ok in bash v5.0.17 (Fedora 33) w stty v8.32, but I am noticing that it doesn't seem as portable as other options. For example, the same command run in Termux (Android Linux terminal port on F-Droid) gives me the error bash: : No such file or directory. Meanwhile, the simpler rows=$(stty size|awk '{print $1}') and columns=$(stty size|awk '{print $2}') works fine under either Fedora on the desktop or Termux on the phone. – zpangwin May 25 '21 at 03:06
18

tput cols and tput lines query the size of the terminal (from the terminal device driver, not the terminal itself) from the terminal device on its stdout, and if stdout is not a terminal device like in the case of cols=$(tput cols) where it's then a pipe, from stderr.

So, to retrieve the values from an arbitrary terminal device, you need to open that device on the stderr of tput:

{ cols=$(tput cols) rows=$(tput lines); } 2< "$TERMINALPATH"

(here open in read-only mode so tput doesn't output its error messages there).

Alternatively, you may be able to use stty size. stty queries the terminal on stdin:

read rows cols < <(stty size < "$TERMINALPATH")

None of those are standard so may (and in practice will) not work on all systems. It should be fairly portable to GNU/Linux systems though.

The addition of stty size or other method to query terminal size was requested to POSIX but the discussion doesn't seem to be going anywhere.

14

This script:

#!/bin/bash

echo "The number of columns are $COLUMNS" echo "The number of lines are $LINES"

Worked here with absolutely nothing more.....

Why you are setting a environment variable with data?

COLUMNS=$(/home/test/Documents/get_columns.sh)

Are you try to get the columns and lines from other script or tty? Is that it? Still strange for me because you're setting the columns environment variable for the local script....

AdminBee
  • 22,803
  • This doesn't help retrieve the values within the OP's cron job for a particular terminal. – Chris Davies Jul 29 '16 at 13:11
  • 1
    Ahn? What? Now i am more confused, how can cronjobs scripts can have width????? They actually not run in a terminal. – Luciano Andress Martini Jul 29 '16 at 16:25
  • I know. The cron job queries a particular terminal for its characteristics. (I'm not entirely sure why it needs to do this, but that's what the OP wants.) – Chris Davies Jul 29 '16 at 16:27
  • @LucianoAndressMartini $COLUMNS and $LINES are bash variables, it does not work (for example) in dash and posh – ingroxd Sep 15 '18 at 17:25
  • Tks. I really know, but my script is hashbang with /bin/bash i think it is supposed for you to know that you should use it. If you are using some unix without bash, maybe my answer not for you. – Luciano Andress Martini Sep 16 '18 at 12:39
5

My answer is different than that of Roaima's, since it is dynamic. His/Hers answer gives you the size of the terminal at creation. If you are for example using a tiling window manager, like i3 or bspwm, you would rather want to have the current width of the terminal. Thus I use ssty from the coreutils package:

#!/bin/bash
stty size | awk '{print $2}'

Luciano's solution works flawless in xterm and xfce4-terminal. I dont know if all terminals set the $COLUMNS variable.

Sasha
  • 151
  • 1
  • 4