bcrowell2 has asked for the wisdom of the Perl Monks concerning the following question:

O Monks,

I'm looking for a completely bulletproof pure-perl way of finding the width of the terminal that will work on a default install of perl on Linux or BSD, i.e., with no dependencies at all except on modules that are included with every perl distribution. The following is the closest I've been able to come. I believe it will work on any Linux-based perl installation (because Term::ReadLine is standard, and is implemented as Term::ReadLine::Gnu), but on my FreeBSD box it didn't work until I added an optional library (either Term::ReadLine::Gnu or Term::ReadKey). Can anyone suggest any pure-perl method that will work by default on non-Gnu systems as well?

Other ideas I've thought of: The $COLUMNS shell variable is bash-specific, and isn't an environment variable, so it isn't exported via %ENV. Doing a shell command doesn't seem to work, because the spawned shell isn't associated with a terminal, and therefore doesn't have a width associated with it.

I couldn't find much documentation for Term::ReadLine::Perl, but I'm thinking maybe it's a pure-perl implementation that I might be able to steal a few lines of code out of that would accomplish what I want...? The source code is pretty lengthy, so I haven't been successful with casual attempts to figure this out.

TIA!

-Ben

sub columns { my $result; # http://search.cpan.org/~kjalb/TermReadKey/ReadKey.pm # Term::ReadKey is not a standard Perl module, and may not be instal +led. If the user resizes the terminal # while the program is running, this will correctly reflect the resi +zing. eval 'use Term::ReadKey; my ($wchar, $hchar, $wpixels, $hpixels) = G +etTerminalSize(); $result=$wchar'; # http://search.cpan.org/~nwclark/perl-5.8.8/lib/Term/ReadLine.pm # Term::ReadLine is a standard Perl module, but exists in different +implementations under the hood. On a Linux # system, it's implemented using Term::ReadLine::Gnu, which supports + get_screen_size(). On a default FreeBSD system, # however, the following won't work; you'd have to install the p5-Re +adLine-Gnu package to get support for this function. if (!$result) { eval 'use Term::ReadLine; $term = new Term::ReadLine("foo"); my ($ +r,$c)= Term::ReadLine::get_screen_size(); $result=$c'; } return $result; }

Replies are listed 'Best First'.
Re: bulletproof way of finding size of terminal?
by bruceb3 (Pilgrim) on Aug 31, 2007 at 23:07 UTC
    Ask the operating system.
    #!/usr/bin/env perl use strict; use warnings; chomp(my @lines = `stty -a`); my ($rows, $columns); for (@lines) { $rows = $1 if /rows (\d+);/; # linux $rows = $1 if /(\d+) rows;/; # FreeBSD $columns = $2 if /columns (\d+);/; # line $columns = $1 if /(\d+) columns;/; # FreeBSD } print "rows $rows columns $columns\n";
    Outputs;
    rows 45 columns 118
    On my FreeBSD clone.

      Fantastic -- thanks, that's exactly what I needed. BTW, I think the $2 should have been a $1. It works for me on both Linux and FreeBSD. The following is what I ended up with.

      # Normally returns the number of columns on the output tty. # Return 0 if output isn't a tty. # Returns undef if it's unable to find the width. sub tty_columns { return 0 unless -t STDOUT; my $result; # The following works on linux, but seems to fail on freebsd. # It works properly if the user resizes the terminal window while th +e program is running. if (eval "require 'sys/ioctl.ph'") { eval { # All of the dies on the next few lines will be caught by the ev +al{}. die unless defined &TIOCGWINSZ; open(TTY, "+</dev/tty") or die; die unless ioctl(TTY, &TIOCGWINSZ, $winsize=''); my ($row, $col, $xpixel, $ypixel) = unpack('S4', $winsize); $result = $col; } } return $result if defined $result; # A less efficient fallback, should work on anything unixy. chomp(my @lines = `stty -a`); my ($rows, $columns); for (@lines) { $rows = $1 if /rows (\d+);/; # linux $rows = $1 if /(\d+) rows;/; # FreeBSD $columns = $1 if /columns (\d+);/; # linux $columns = $1 if /(\d+) columns;/; # FreeBSD } return $columns if defined $columns; # The following two methods give us a fighting chance on a non-POSIX + system. I don't use them as the defaults # because I don't want to introduce dependencies. # http://search.cpan.org/~kjalb/TermReadKey/ReadKey.pm # Term::ReadKey is not a standard Perl module, and may not be instal +led. If the user resizes the terminal # while the program is running, this will correctly reflect the resi +zing. eval 'use Term::ReadKey; my ($wchar, $hchar, $wpixels, $hpixels) = G +etTerminalSize(); $result=$wchar'; return $result if defined $result; # http://search.cpan.org/~nwclark/perl-5.8.8/lib/Term/ReadLine.pm # Term::ReadLine is a standard Perl module, but exists in different +implementations under the hood. On a Linux # system, it's implemented using Term::ReadLine::Gnu, which supports + get_screen_size(). On a default FreeBSD system, # however, the following won't work; you'd have to install the p5-Re +adLine-Gnu package to get support for this function. eval 'use Term::ReadLine; $term = new Term::ReadLine("foo"); my ($r, +$c)= Term::ReadLine::get_screen_size(); $result=$c'; return $result if defined $result; return undef; }
Re: bulletproof way of finding size of terminal?
by Joost (Canon) on Aug 31, 2007 at 19:23 UTC
      Here's a litle sub I wrote a while back. For once, I kept some decent comments on where I found it to work, and not work.
      # $ENV{COLUMNS} isn't set when running through putty (unless from with +in emacs) # GetTerminalSize returns 0 from emacs, but ok from putty # fallback to 72 if the other two methods fail sub get_terminal_width { -t STDOUT && return (GetTerminalSize)[0] || $ENV{COLUMNS} || 72; return 72; }

      The FAQ postings are the only thing I miss from clpmisc

        Hnmn...that doesn't work for me. The $ENV{COLUMNS} is, I think, guaranteed never to work, since COLUMNS isn't an environment variable. The (GetTerminalSize)[0] is, I assume, depending on some module?
      Aha -- interesting, thanks! The one thing that's a drag is that the ioctl method doesn't seem to work when you're ssh'd in (even though -T STDOUT is true). Other than that, it's exactly what I need.
Re: bulletproof way of finding size of terminal?
by samtregar (Abbot) on Aug 31, 2007 at 18:10 UTC
      I'm looking for a method that doesn't introduce any dependencies on modules that aren't part of the standard perl distribution. The code I posted works already, but requires a module that's optional on BSD.
        Oh, how boring/wrong/silly. If you can't use CPAN, why are you using Perl?

        -sam