Jak znaleźć szerokość i wysokość okna terminala?

295

Jako prosty przykład chcę napisać skrypt CLI, który może drukować =na całej szerokości okna terminala.

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

lub

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

lub

#!/usr/bin/env bash
x=0
while [ $x -lt ??? ]; do echo -n '='; let x=$x+1 done; echo
za dużo php
źródło
Stworzyłem tę małą bibliotekę node.js, aby konsekwentnie uzyskiwać prawidłowy rozmiar okna npmjs.com/package/window-size
jonschlinkert

Odpowiedzi:

562
  • tput cols podaje liczbę kolumn.
  • tput lines podaje liczbę wierszy.
TonyUser
źródło
11
echo -e "lines\ncols"|tput -Saby uzyskać zarówno wiersze, jak i kolumny, patrz: linux.about.com/library/cmd/blcmdl1_tput.htm
nickl-
4
tputto świetne polecenie z wieloma poleceniami do odczytywania stanu terminala, kontrolowania właściwości kursora i tekstu i tak dalej.
Drew Noakes,
2
Przydatny alias, na przykład alias dim="echo $(tput cols)x$(tput lines)":, który może spowodować 80x50.
biskup
2
Te pytania i odpowiedzi prawdopodobnie należą do stron SE w systemie Unix lub superuser.
mydoghasworms
2
@bishop podana komenda alias jest oceniana, gdy powłoka jest pobierana Musisz użyć pojedynczych cudzysłowów dla polecenia aliasu. Tak jak: alias dim='echo Terminal Dimensions: $(tput cols) columns x $(tput lines) rows'
brandonsimpkins
103

W bash, zmienne środowiskowe $LINESi $COLUMNSpowinny być w stanie załatwić sprawę. Zostanie ustawiony automatycznie po każdej zmianie rozmiaru terminala. (tj. sygnał SIGWINCH )

David Dean
źródło
18
Jednak te zmienne środowiskowe są dostępne tylko dla bash, a nie dla programów uruchamianych w bash (takich jak perl, python, ruby).
Br.Bill
9
To nie działa w niczym innym niż interaktywna sesja bash (jeśli uruchomisz skrypt, nie będzie już interaktywny). Jedynym miejscem, w którym możesz go użyć w skrypcie, jest polecenie zachęty w bash.
Doncho Gunchev
1
Właściwie to działa w nieinteraktywnych skryptach, jeśli ustawisz tę checkwinsizeopcję. Na przykład ten nieinteraktywny skrypt wyświetli wymiary terminala, na którym jest uruchomiony: shopt -s checkwinsize; (:); echo $LINES $COLUMNS( checkwinsizeopcja inicjuje zmienne tylko po oczekiwaniu na zakończenie podpowłoki, dlatego potrzebujemy (:)instrukcji)
user3340662
$LINESi $COLUMNSsą aktualizowane po SIGWINCHwysłaniu, w rzeczywistości po wykonaniu dowolnego interaktywnego polecenia. Jeśli próby aktualizacji PS1z trap SIGWINCHwas nie może używać $LINESi $COLUMNSprzechowywać je stare wartości ((
gavenkoa
LINESi COLUMNSsą ustawiane tylko jako zmienne powłoki przez bash. Bash nie ustawi ich jako zmiennych środowiskowych , chyba że wyeksportujesz te zmienne powłoki.
Markus Kuhn
67

I jest sttyz coreutils

$ stty size
60 120 # <= sample output

Wyświetli liczbę wierszy i kolumn lub odpowiednio wysokość i szerokość.

Następnie możesz użyć albo cutalbo, awkaby wyodrębnić żądaną część.

Dotyczy to stty size | cut -d" " -f1wysokości / linii i stty size | cut -d" " -f2szerokości / kolumn

ryenus
źródło
Ten styl nie może współpracować z PIPE, sugeruj użycie stylu tput.
liuyang1
6
Problem z tput polega na tym, że nie zawsze jest dostępny, podczas gdy stty jest dostępne w każdym tty. dzięki za te informacje!
iRaS,
16
yes = | head -n$(($(tput lines) * $COLUMNS)) | tr -d '\n'
pixelbeat
źródło
3
Nie bezpośrednia odpowiedź na pytanie, ale świetny skrypt demo.
Chris Page
Cóż za wspaniały przykład!
Kurt Zhong
1
jak do cholery tęskniłem za trkomendą przez te wszystkie lata? (facepalm)
Marco Massenzio
Jaki to język? Wygląda niejasno jak skrypt powłoki z błędami składniowymi. (W skorupce nie możesz mieć spacji wokół znaku równości zadania, a pierwsza rura wydaje się nie na miejscu.)
tripleee
2
yes '='wyświetli nieskończoną liczbę wierszy „=”, a następujące polecenia zorganizują wystarczająco dużo, aby wypełnić terminal
pixelbeat
12

Aby to zrobić w środowisku Windows CLI, najlepszym sposobem, jaki mogę znaleźć, jest użycie polecenia mode i parsowanie danych wyjściowych.

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;
}

Mam nadzieję, że się przyda!

UWAGA : Zwrócona wysokość to liczba linii w buforze, a nie liczba linii widocznych w oknie. Jakieś lepsze opcje?

liceus
źródło
3
Zwróć uwagę na problem z tym: wynik tego polecenia jest specyficzny dla ustawień regionalnych. Innymi słowy, nie będzie działać tak, jak jest w innym języku Windows. Oto, co otrzymuję w systemie Windows 7: i.imgur.com/Wrr7sWY.png
Camilo Martin
Dodano odpowiedź z rozwiązaniem tego problemu. +1!
Camilo Martin
10

W systemie POSIX ostatecznie chcesz wywoływać wywołanie TIOCGWINSZ(Get WINdow SiZe) ioctl(). Większość języków powinna mieć do tego jakieś opakowanie. Np. W Perlu możesz użyć Term :: Size :

use Term::Size qw( chars );

my ( $columns, $rows ) = chars \*STDOUT;
LeoNerd
źródło
1
Dzięki za to - poprowadził mnie we właściwym kierunku. Eliksir: :io.columnsErlang: io:columns(). erlang.org/doc/man/io.html#columns-0
Henrik N
2
Nie ma go TIOCGWINSZw standardzie POSIX i ioctl()jest zdefiniowany tylko dla przestarzałej funkcji STREAMS.
osvein
4

Jak wspomniałem w odpowiedzi Lyceusza, jego kod zawiedzie w nieanglojęzycznych ustawieniach regionalnych Windows, ponieważ wtedy wyjście modemoże nie zawierać podciągów „kolumn” lub „linii”:

                                         wyjście polecenia trybu

Możesz znaleźć poprawny podciąg bez szukania tekstu:

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

Zauważ, że nawet nie zawracam sobie głowy liniami, ponieważ są niewiarygodne (a tak naprawdę nie dbam o nie).

Edycja: Zgodnie z komentarzami na temat systemu Windows 8 (oh ...

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

Przetestuj to, bo tego nie przetestowałem.

Camilo Martin
źródło
Twoja metoda nie działa w Win8. Dostaję więcej niż jedną ---linię. i.imgur.com/4x02dqT.png
Piątek
@ Mark Cóż, świetnie, to jest po prostu PIĘKNE. Dziękuję Windows. <3 (na bardziej odpowiednią notatkę: Zobaczę, jak to naprawić ... kiedy wyjdzie Windows 9: P).
Camilo Martin
To jest tak, jak to zrobić: $mode = `mode`; list($rows, $cols) = array_slice(preg_split('/\n/', substr($mode, strpos($mode, 'CON:'))), 2, 2);. A potem po prostu wymieniam wszystko oprócz liczb.
Aleksandr Makov
@AleksandrMakov Zastanawiam się, co się stanie, jeśli istnieją języki z porządkiem jak CON device status:? Może dopasowanie czegoś podobnego CON.*:działałoby lepiej.
Camilo Martin
1
@ Mark Właściwie przesłuchiwałem dokładnie tę rzecz. Dlaczego do cholery to zrobiłem? Wątpię, po prostu założyłem, że istnieje jakiś powód i poszedłem z nim, lol.
Camilo Martin,
1

Zainspirowany odpowiedzią @ pixelbeat, oto powstały poziomy pasek tput, niewielkie niewłaściwe użycie printfwypełnienia / wypełnienia itr

printf "%0$(tput cols)d" 0|tr '0' '='
Huoneusto
źródło
0

W niektórych przypadkach wiersze / LINIE i kolumny nie pasują do rzeczywistego rozmiaru używanego „terminala”. Być może nie masz dostępnego „tput” lub „stty”.

Oto funkcja bash, której możesz użyć do wizualnego sprawdzenia rozmiaru. To zadziała do 140 kolumn x 80 wierszy. W razie potrzeby możesz dostosować maksima.

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
źródło