Jak uzyskać szerokość okna konsoli Linux w Pythonie

264

Czy w Pythonie jest sposób na programowe określenie szerokości konsoli? Mam na myśli liczbę znaków, która mieści się w jednej linii bez zawijania, a nie szerokość okna w pikselach.

Edytować

Szukasz rozwiązania, które działa w systemie Linux

Siergiej Golovchenko
źródło
Spójrz na tę odpowiedź, aby uzyskać bardziej rozbudowane rozwiązanie mające mechanizm drukowania zależny od kolumn. stackoverflow.com/questions/44129613/…
Riccardo Petraglia

Odpowiedzi:

261
import os
rows, columns = os.popen('stty size', 'r').read().split()

używa komendy 'stty size', która zgodnie z wątkiem na liście mailingowej Pythona jest dość uniwersalna na Linuksie. Otwiera polecenie „rozmiar stty” jako plik, „odczytuje” z niego i wykorzystuje prosty podział łańcucha w celu oddzielenia współrzędnych.

W przeciwieństwie do wartości os.environ [„COLUMNS”] (do której nie mam dostępu pomimo używania bash jako mojej standardowej powłoki), dane również będą aktualne, podczas gdy uważam, że os.environ [„COLUMNS”] wartość będzie obowiązywać tylko w czasie uruchamiania interpretera python (załóżmy, że użytkownik zmienił rozmiar okna od tego czasu).

(Zobacz odpowiedź @GringoSuave na temat tego, jak to zrobić w Pythonie 3.3+)

brokkr
źródło
1
Możesz sprawić, aby działał również w systemie Solaris, jeśli zamiast „size” zdasz „-a”. W wynikach rozdzielanych średnikami będzie „wiersze = Y; kolumny = X”.
Joseph Garvin
5
COLUMNS nie jest domyślnie eksportowany w Bash, dlatego os.environ [„COLUMNS”] nie działa.
Kjell Andreassen,
38
rows, columns = subprocess.check_output(['stty', 'size']).split()jest trochę krótszy, a podproces to przyszłość
cdosborn
7
tput jest lepszy niż stty, ponieważ stty nie może współpracować z PIPE.
liuyang1
5
rows, columns = subprocess.check_output(['stty', 'size']).decode().split()Jeśli chcesz ciągów znaków Unicode dla kompatybilności py2 / 3
Bryce Guinta
264

Nie jestem pewien, dlaczego znajduje się w module shutil, ale wylądował tam w Pythonie 3.3, Sprawdzając rozmiar terminala wyjściowego :

>>> import shutil
>>> shutil.get_terminal_size((80, 20))  # pass fallback
os.terminal_size(columns=87, lines=23)  # returns a named-tuple

Implementacja niskiego poziomu znajduje się w module OS. Działa również w systemie Windows.

Backport jest teraz dostępny dla Python 3.2 i poniżej:

Gringo Suave
źródło
25
To dlatego, że nie powinieneś już używać 2.7, więc przejdź do 3.x, warto.
anthonyryan1
5
@osirisgothra Wielu dostawców hostingu nie obsługuje jeszcze Python3, więc niektórzy z nas są zmuszeni używać Python2 do programowania zaplecza. Chociaż nie powinno to mieć nic wspólnego z powiększaniem terminala ...
białobrody
4
@osirisgothra Ponieważ jest dużo kodu Python 2, którego przeniesienie wymagałoby zbyt wiele pracy. Ponadto Pypy nadal ma dość słabą obsługę Python 3.
Antymon
2
@whitebeard Czy istnieje powód, dla którego subskrybent nie może zainstalować Pythona 3 na VPS? Czy w 2016 roku ludzie nadal korzystają z hostingu współdzielonego, którego administrator nie chce instalować Python 3? Na przykład został python3.5zainstalowany hosting współdzielony WebFaction .
Damian Yerrick
2
+1 dla rozwiązania, które działa również, gdy standardowe wejście zostało przekierowane z pliku! W przypadku innych rozwiązań albo otrzymywałem Inappropriate ioctl for devicebłędy / ostrzeżenia, albo otrzymałem zdefiniowaną wartość rezerwową 80.
pawamoy
65

posługiwać się

import console
(width, height) = console.getTerminalSize()

print "Your terminal's width is: %d" % width

EDYCJA : och, przepraszam. To nie jest standardowa wersja Pythona, oto źródło pliku console.py (nie wiem skąd).

Moduł wydaje się działać w ten sposób: sprawdza, czy termcapjest dostępny, kiedy tak. Wykorzystuje to; jeśli nie, sprawdza, czy terminal obsługuje specjalne ioctlwywołanie i to też nie działa, sprawdza zmienne środowiskowe, które eksportują niektóre powłoki. Prawdopodobnie będzie to działać tylko w systemie UNIX.

def getTerminalSize():
    import os
    env = os.environ
    def ioctl_GWINSZ(fd):
        try:
            import fcntl, termios, struct, os
            cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
        '1234'))
        except:
            return
        return cr
    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
    if not cr:
        try:
            fd = os.open(os.ctermid(), os.O_RDONLY)
            cr = ioctl_GWINSZ(fd)
            os.close(fd)
        except:
            pass
    if not cr:
        cr = (env.get('LINES', 25), env.get('COLUMNS', 80))

        ### Use get(key[, default]) instead of a try/catch
        #try:
        #    cr = (env['LINES'], env['COLUMNS'])
        #except:
        #    cr = (25, 80)
    return int(cr[1]), int(cr[0])
Johannes Weiss
źródło
5
Dzięki za szybką odpowiedź, ale tutaj ( effbot.org/zone/console-handbook.htm ) napisano, że „Moduł konsoli jest obecnie dostępny tylko dla Windows 95, 98, NT i 2000”. Szukam rozwiązania, które działa w systemie Linux. Prawdopodobnie nie było jasne z tagu, odpowiednio zmodyfikuję pytanie.
Sergey Golovchenko
2
ponieważ tego modułu „konsoli”, którego używasz, nie ma w standardowej bibliotece Pythona, powinieneś podać jego kod źródłowy lub przynajmniej link do niego.
nosklo
Przykro mi z tego powodu. W rzeczywistości nie znałem tego modułu. Próbowałem zaimportować konsolę i zadziałało, użyłem konsoli. Pojawiły się <tab> <tab> i getTerminalSize (). Zamiast patrzeć, skąd pochodzi, już opublikowałem odpowiedź, ponieważ miałem szczęście z prostoty g
Johannes Weiss
Może patrzę na inny moduł „konsoli”, czy możesz podać link do tego, który masz?
Sergey Golovchenko
4
Och, i nie nakładaj kodu, ale „cr” jest mylącą nazwą, ponieważ implikuje krotkę (kolumny, wiersze). W rzeczywistości jest odwrotnie.
Paul Du Bois,
57

Powyższy kod nie zwrócił poprawnego wyniku na moim systemie Linux, ponieważ winsize-struct ma 4 spodenki bez podpisu, a nie 2 spodenki z podpisem:

def terminal_size():
    import fcntl, termios, struct
    h, w, hp, wp = struct.unpack('HHHH',
        fcntl.ioctl(0, termios.TIOCGWINSZ,
        struct.pack('HHHH', 0, 0, 0, 0)))
    return w, h

hp i hp powinny zawierać szerokość i wysokość pikseli, ale nie.

pascal
źródło
4
Tak właśnie należy to zrobić; zwróć uwagę, że jeśli zamierzasz drukować na terminalu, powinieneś użyć '1' jako deskryptora pliku (pierwszy argument ioctl), ponieważ stdin może być potokiem lub innym tty.
mic_e
1
Być może 0 powinno być zastąpione przezfcntl.ioctl(sys.stdin.fileno(), ...
raylu
4
to najlepsza odpowiedź - Twoi użytkownicy będą szczęśliwi, że podproces niespodziewany nie wydarzy się tylko po to, aby uzyskać szerokość terminu
zzzeek,
4
to jest rzeczywiście najczystsza odpowiedź. Myślę jednak, że powinieneś użyć stdoutlub stderrzamiast stdintego. stdinmoże równie dobrze być fajką. Możesz także dodać linię taką jak if not os.isatty(0): return float("inf").
mic_e
działa jakoś na terminalu Chromebook, który nie ma żadnej funkcjonalności. +1
HyperNeutrino
39

Rozejrzałem się i znalazłem rozwiązanie dla systemu Windows pod adresem:

http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/

i rozwiązanie dla systemu Linux tutaj.

Oto wersja, która działa zarówno na systemie Linux, OS X, jak i Windows / Cygwin:

""" getTerminalSize()
 - get width and height of console
 - works on linux,os x,windows,cygwin(windows)
"""

__all__=['getTerminalSize']


def getTerminalSize():
   import platform
   current_os = platform.system()
   tuple_xy=None
   if current_os == 'Windows':
       tuple_xy = _getTerminalSize_windows()
       if tuple_xy is None:
          tuple_xy = _getTerminalSize_tput()
          # needed for window's python in cygwin's xterm!
   if current_os == 'Linux' or current_os == 'Darwin' or  current_os.startswith('CYGWIN'):
       tuple_xy = _getTerminalSize_linux()
   if tuple_xy is None:
       print "default"
       tuple_xy = (80, 25)      # default value
   return tuple_xy

def _getTerminalSize_windows():
    res=None
    try:
        from ctypes import windll, create_string_buffer

        # stdin handle is -10
        # stdout handle is -11
        # stderr handle is -12

        h = windll.kernel32.GetStdHandle(-12)
        csbi = create_string_buffer(22)
        res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
    except:
        return None
    if res:
        import struct
        (bufx, bufy, curx, cury, wattr,
         left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
        sizex = right - left + 1
        sizey = bottom - top + 1
        return sizex, sizey
    else:
        return None

def _getTerminalSize_tput():
    # get terminal width
    # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window
    try:
       import subprocess
       proc=subprocess.Popen(["tput", "cols"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
       output=proc.communicate(input=None)
       cols=int(output[0])
       proc=subprocess.Popen(["tput", "lines"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
       output=proc.communicate(input=None)
       rows=int(output[0])
       return (cols,rows)
    except:
       return None


def _getTerminalSize_linux():
    def ioctl_GWINSZ(fd):
        try:
            import fcntl, termios, struct, os
            cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'1234'))
        except:
            return None
        return cr
    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
    if not cr:
        try:
            fd = os.open(os.ctermid(), os.O_RDONLY)
            cr = ioctl_GWINSZ(fd)
            os.close(fd)
        except:
            pass
    if not cr:
        try:
            cr = (env['LINES'], env['COLUMNS'])
        except:
            return None
    return int(cr[1]), int(cr[0])

if __name__ == "__main__":
    sizex,sizey=getTerminalSize()
    print  'width =',sizex,'height =',sizey
Harco Kuppens
źródło
Zaoszczędziłeś mi czas na robienie tego sam. Działa w systemie Linux. Powinien również działać w systemie Windows. Dzięki!
Steve V.
30

To albo:

import os
columns, rows = os.get_terminal_size(0)
# or
import shutil
columns, rows = shutil.get_terminal_size()

Ta shutilfunkcja jest po prostu owijana wokół, osktóra wychwytuje błędy i ustawia rezerwę, jednak ma jedno ogromne zastrzeżenie - psuje się podczas orurowania! , co jest dość dużą sprawą.
Aby uzyskać rozmiar terminala podczas instalacji rurowej, użyj os.get_terminal_size(0)zamiast tego.

Pierwszy argument 0 to argument wskazujący, że zamiast domyślnego standardowego wyjścia należy użyć deskryptora pliku stdin. Chcemy użyć stdin, ponieważ stdout odłącza się podczas potoku, co w tym przypadku powoduje błąd.

Próbowałem dowiedzieć się, kiedy warto użyć argumentu stdout zamiast argumentu stdin i nie mam pojęcia, dlaczego jest to ustawienie domyślne.

Granitozaur
źródło
3
os.get_terminal_size()został wprowadzony w Pythonie 3.3
villapx
Najpierw spróbowałem shutil i zadziałało za pierwszym razem. Używam Python 3.6+.
Dan
Użycie os.get_terminal_size(0)spowoduje awarię, jeśli potokujesz do standardowego wejścia. Spróbuj:echo x | python3 -c 'import os; print(os.get_terminal_size(0))'
ehabkost
19

Począwszy od Python 3.3 jest to proste: https://docs.python.org/3/library/os.html#querying-the-size-of-a-terminal

>>> import os
>>> ts = os.get_terminal_size()
>>> ts.lines
24
>>> ts.columns
80
Bob Enohp
źródło
16
shutil.get_terminal_size() is the high-level function which should normally be used, os.get_terminal_size is the low-level implementation.
mid_kid
1
To jest prawie kopia podanej powyżej rocznej odpowiedzi.
Gringo Suave,
6

Wygląda na to, że istnieją pewne problemy z tym kodem, Johannes:

  • getTerminalSize musi import os
  • co jest env? wygląda os.environ.

Ponadto, po co przełączać linesi colsprzed powrotem? Jeśli TIOCGWINSZi sttyobaj powiedzą linesto cols, mówię, zostaw to w ten sposób. Zdezorientowało mnie to przez dobre 10 minut, zanim zauważyłem niekonsekwencję.

Sridhar, nie dostałem tego błędu, gdy przesyłałem dane wyjściowe. Jestem prawie pewien, że został złapany prawidłowo w try-wyjątkiem.

pascal, "HHHH"nie działa na moim komputerze, ale "hh"działa. Miałem problem ze znalezieniem dokumentacji dla tej funkcji. Wygląda na to, że zależy od platformy.

chochem, włączony.

Oto moja wersja:

def getTerminalSize():
    """
    returns (lines:int, cols:int)
    """
    import os, struct
    def ioctl_GWINSZ(fd):
        import fcntl, termios
        return struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234"))
    # try stdin, stdout, stderr
    for fd in (0, 1, 2):
        try:
            return ioctl_GWINSZ(fd)
        except:
            pass
    # try os.ctermid()
    try:
        fd = os.open(os.ctermid(), os.O_RDONLY)
        try:
            return ioctl_GWINSZ(fd)
        finally:
            os.close(fd)
    except:
        pass
    # try `stty size`
    try:
        return tuple(int(x) for x in os.popen("stty size", "r").read().split())
    except:
        pass
    # try environment variables
    try:
        return tuple(int(os.getenv(var)) for var in ("LINES", "COLUMNS"))
    except:
        pass
    # i give up. return default.
    return (25, 80)
thejoshwolfe
źródło
Ja też błąkałem się envi rzeczywiście wynika to env = os.environz przyjętej odpowiedzi .
sdaau
6

Wiele implementacji języka Python 2 tutaj zawiedzie, jeśli podczas wywoływania tego skryptu nie będzie terminalu sterującego. Możesz sprawdzić sys.stdout.isatty (), aby ustalić, czy w rzeczywistości jest to terminal, ale to wykluczy kilka przypadków, więc uważam, że najbardziej pythonicznym sposobem ustalenia rozmiaru terminalu jest użycie wbudowanego pakietu curses.

import curses
w = curses.initscr()
height, width = w.getmaxyx()
wonton
źródło
2

Próbowałem stąd rozwiązania, które wzywa do stty size:

columns = int(subprocess.check_output(['stty', 'size']).split()[1])

Jednak dla mnie to nie powiodło się, ponieważ pracowałem nad skryptem, który oczekuje przekierowanego wejścia na standardowe wejście, i stty narzekałby, że „stdin nie jest terminalem” w tym przypadku.

Byłem w stanie sprawić, aby działało w następujący sposób:

with open('/dev/tty') as tty:
    height, width = subprocess.check_output(['stty', 'size'], stdin=tty).split()
Marc Liyanage
źródło
2

Spróbuj „błogosławieństw”

Szukałem tego samego. Jest bardzo łatwy w użyciu i oferuje narzędzia do kolorowania, stylizacji i pozycjonowania w terminalu. To, czego potrzebujesz, jest tak proste, jak:

from blessings import Terminal

t = Terminal()

w = t.width
h = t.height

Działa jak urok w Linuksie. (Nie jestem pewien co do MacOSX i Windows)

Pobierz i dokumentację tutaj

lub możesz zainstalować za pomocą pipa:

pip install blessings
Iman Akbari
źródło
Dzisiaj powiedziałbym, że spróbuj „błogosławiony”, który jest obecnie utrzymywanym rozwidleniem (i ulepszeniem) „błogosławieństw”.
zezollo
1

Odpowiedź @ reannual działa dobrze, ale jest z tym problem: os.popen jest teraz przestarzała . subprocessModuł powinien być stosowany zamiast, więc oto wersja użytkownika @ reannual kod, który używa subprocessi jest bezpośrednio odpowiada na pytanie (podając szerokość kolumny bezpośrednio jako int:

import subprocess

columns = int(subprocess.check_output(['stty', 'size']).split()[1])

Testowane na OS X 10.9

rickcnagy
źródło
1

Jeśli używasz Pythona w wersji 3.3 lub nowszej, polecam wbudowaną wersję, get_terminal_size()jak już zalecono. Jeśli jednak utkniesz w starszej wersji i chcesz prostego, wieloplatformowego sposobu na zrobienie tego, możesz użyć asciimatics . Ten pakiet obsługuje wersje Pythona z powrotem do wersji 2.7 i korzysta z opcji podobnych do tych sugerowanych powyżej, aby uzyskać bieżący rozmiar terminala / konsoli.

Wystarczy zbudować Screenklasę i użyć dimensionswłaściwości, aby uzyskać wysokość i szerokość. Udowodniono, że działa to w systemach Linux, OSX i Windows.

Aha - i pełne ujawnienie tutaj: jestem autorem, więc nie krępuj się otworzyć nowy problem, jeśli masz jakiekolwiek problemy z jego uruchomieniem.

Peter Brittain
źródło
0

Oto wersja, która powinna być kompatybilna z Linux i Solaris. Na podstawie postów i zamówień z madchine . Wymaga modułu podprocesu.

def Termsize ():
    importuj shlex, podproces, re
    output = subprocess.check_output (shlex.split ('/ bin / stty -a'))
    m = re.search ('wiersze \ D + (? P \ d +); kolumny \ D + (? P \ d +);', wynik)
    jeśli m:
        zwraca m.group („wiersze”), m.group („kolumny”)
    podnieść OSError („Zła odpowiedź:% s”% (wynik))
>>> Termsize ()
(„40”, „100”)
Derrick Petzold
źródło