Как получить ширину окна консоли Linux в Python

264

Есть ли в Python способ программно определить ширину консоли? Я имею в виду количество символов, которые помещаются в одну строку без переноса, а не ширину окна в пикселях.

редактировать

Ищете решение, которое работает на Linux

Сергей Головченко
источник
Посмотрите этот ответ для более обширного решения, чтобы иметь «зависимый от столбцов» механизм печати. stackoverflow.com/questions/44129613/…
Риккардо Петраглия

Ответы:

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

использует команду 'stty size', которая, согласно потоку в списке рассылки python, является достаточно универсальной в linux. Он открывает команду «stty size» в виде файла, «читает» из нее и использует простое разбиение строки для разделения координат.

В отличие от значения os.environ ["COLUMNS"] (к которому я не могу получить доступ, несмотря на использование bash в качестве стандартной оболочки), данные также будут современными, тогда как я считаю, что os.environ ["COLUMNS"] Значение будет действительным только для времени запуска интерпретатора Python (предположим, что пользователь изменил размер окна с тех пор).

(Смотрите ответ @GringoSuave о том, как это сделать на Python 3.3+)

brokkr
источник
1
Вы можете заставить это работать и в Solaris, если вместо «size» вы передадите «-a». В выходных данных, разделенных точкой с запятой, будет "row = Y; columns = X".
Джозеф Гарвин
5
COLUMNS не экспортируется по умолчанию в Bash, поэтому os.environ ["COLUMNS"] не работает.
Кьелл Андреассен
38
rows, columns = subprocess.check_output(['stty', 'size']).split()немного короче, плюс подпроцесс - будущее
cdosborn
7
tput лучше, чем stty, так как stty не может работать с PIPE.
liuyang1
5
rows, columns = subprocess.check_output(['stty', 'size']).decode().split()Если вам нужны строки в кодировке Юникод для совместимости с py2 / 3
Брайс Гуинта
264

Не уверен, почему он находится в модуле shutil, но он приземлился там в Python 3.3, запрашивая размер выходного терминала :

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

Низкоуровневая реализация находится в модуле os. Также работает в Windows.

Бэкпорт теперь доступен для Python 3.2 и ниже:

Гринго Суаве
источник
25
Это потому, что вы не должны больше использовать 2.7, сделайте переход на 3.x, это того стоит.
anthonyryan1
5
@osirisgothra Многие хостинг-провайдеры пока не поддерживают python3, поэтому некоторые из нас вынуждены использовать python2 для серверной разработки. Хотя это не должно иметь ничего общего с получением размера терминала ...
whitebeard
4
@osirisgothra Потому что есть много кода Python 2, для переноса которого потребуется слишком много работы. Кроме того, Pypy все еще имеет слабую поддержку Python 3.
Сурьма
2
@whitebeard Есть ли причина, по которой подписчик не может установить Python 3 на VPS? Или в 2016 году люди все еще используют виртуальный хостинг, администратор которого не хочет устанавливать Python 3? Например, установлен общий хостинг WebFaction python3.5.
Дамиан Йеррик
2
+1 за решение, которое также работает, когда стандартный ввод был перенаправлен из файла! С другими решениями я либо получал сообщения об Inappropriate ioctl for deviceошибках / предупреждениях, либо получал определенное запасное значение 80.
pawamoy
65

использование

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

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

РЕДАКТИРОВАТЬ : о, я извиняюсь. Это не стандартная библиотека Python, вот источник console.py (я не знаю, откуда он).

Модуль, кажется, работает так: он проверяет, termcapдоступен ли, когда да. Это использует это; если нет, он проверяет, поддерживает ли терминал специальный ioctlвызов, и это тоже не работает, он проверяет переменные среды, которые некоторые оболочки экспортируют для этого. Это, вероятно, будет работать только в 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])
Йоханнес Вайс
источник
5
Спасибо за быстрый ответ, но здесь ( effbot.org/zone/console-handbook.htm ) говорится, что «Модуль консоли в настоящее время доступен только для Windows 95, 98, NT и 2000». Я ищу решение, которое работает на Linux. Вероятно, это не было ясно из тега, я отредактирую вопрос соответственно.
Сергей Головченко
2
поскольку используемый вами «консольный» модуль не входит в стандартную библиотеку python, вы должны предоставить его исходный код или хотя бы ссылку на него.
nosklo
Я так сожалею об этом. На самом деле, я не знал этот модуль. Я попытался импортировать консоль, и она работала, я использовал консоль. <Tab> <tab> и getTerminalSize () обнаружились. Вместо того, чтобы искать, откуда он, я уже опубликовал ответ, потому что мне так повезло в простоте. G
Йоханнес Вайс,
Возможно, я смотрю на другой «консольный» модуль, не могли бы вы предоставить ссылку на тот, который у вас есть?
Сергей Головченко
4
Да, и не для того, чтобы накапливать код, но "cr" - это запутанное имя, потому что оно подразумевает, что кортеж есть (столбцы, строки). На самом деле, все наоборот.
Пол Дю Буа
57

Приведенный выше код не дал верного результата в моем linux, потому что winsize-struct имеет 4 шорта без знака, а не 2 шорта со знаком:

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 и hp должны содержать ширину и высоту пикселя, но не должны.

паскаль
источник
4
Вот как это должно быть сделано; обратите внимание, что если вы собираетесь печатать в терминал, вы должны использовать '1' в качестве дескриптора файла (первый аргумент ioctl), так как stdin может быть каналом или каким-то другим tty.
mic_e
1
Возможно, 0 следует заменить наfcntl.ioctl(sys.stdin.fileno(), ...
raylu
4
это лучший ответ - ваши пользователи будут рады, что не будет неожиданного подпроцесса, происходящего только для того, чтобы получить ширину термина
zzzeek
4
это действительно самый чистый ответ. Я думаю, что вы должны использовать stdoutили stderrвместо stdin, хотя. stdinвполне может быть труба. Вы также можете добавить строку, например if not os.isatty(0): return float("inf").
mic_e
это как-то работает на терминале Chromebook, который не имеет никакой функциональности. +1
HyperNeutrino
39

Я искал вокруг и нашел решение для Windows на:

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

и решение для Linux здесь.

Итак, вот версия, которая работает как на Linux, OS X и 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
Харко Куппенс
источник
Вы сэкономили мне время, чтобы сделать это сам. Работает на Linux. Должно работать и на Windows. Спасибо!
Стив В.
30

Это либо:

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

Эта shutilфункция является просто оберткой вокруг osтой, которая ловит некоторые ошибки и устанавливает запасной вариант, однако имеет одно огромное предостережение - она ломается при прокачке! Это довольно большое дело.
Чтобы получить размер терминала при использовании трубопровода, используйте os.get_terminal_size(0)вместо этого.

Первый аргумент 0- это аргумент, указывающий, что дескриптор файла stdin должен использоваться вместо стандартного stdout. Мы хотим использовать stdin, потому что stdout отключается, когда он передается по каналу, что в этом случае вызывает ошибку.

Я попытался выяснить, когда имеет смысл использовать stdout вместо аргумента stdin, и понятия не имею, почему это значение по умолчанию.

Granitosaurus
источник
3
os.get_terminal_size()был представлен в Python 3.3
villapx
Сначала я попробовал шутил, и это сработало с первой попытки. Я использую Python 3.6+.
Дан
Использование os.get_terminal_size(0)приведет к сбою, если вы отправите трубку на стандартный ввод Попробуйте:echo x | python3 -c 'import os; print(os.get_terminal_size(0))'
ehabkost
19

Начиная с Python 3.3, это просто: 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
Боб Инохп
источник
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
Это почти копия ответа на год старше, приведенного выше.
Гринго Суаве
6

Похоже, с этим кодом есть некоторые проблемы, Йоханнес:

  • getTerminalSize нуждается в import os
  • что такое env? выглядит как os.environ.

Также зачем переключаться linesи colsдо возвращения? Если TIOCGWINSZи то , и sttyдругое говорят linesтогда cols, я говорю, оставь это так. Это смутило меня на целых 10 минут, прежде чем я заметил несоответствие.

Шридхар, я не получил эту ошибку, когда передал вывод. Я почти уверен, что это правильно поймано в попытке.

Паскаль, "HHHH"не работает на моей машине, но "hh"работает. У меня были проблемы с поиском документации для этой функции. Похоже, это зависит от платформы.

Хохем, включены.

Вот моя версия:

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
источник
Я envтоже бродил по этому поводу , и это действительно env = os.environот принятого ответа .
sdaau
6

Многие из реализаций Python 2 здесь потерпят неудачу, если при вызове этого скрипта не будет управляющего терминала. Вы можете проверить sys.stdout.isatty (), чтобы определить, является ли это на самом деле терминалом, но это исключит кучу случаев, поэтому я считаю, что самый питонный способ выяснить размер терминала - это использовать встроенный пакет curses.

import curses
w = curses.initscr()
height, width = w.getmaxyx()
пангасиус
источник
2

Я пробовал решение отсюда, которое призывает stty size:

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

Однако это не помогло мне, потому что я работал над сценарием, который ожидает перенаправленного ввода на stdin, и sttyжаловался бы, что в этом случае «stdin не терминал».

Я смог заставить его работать так:

with open('/dev/tty') as tty:
    height, width = subprocess.check_output(['stty', 'size'], stdin=tty).split()
Марк Лиянэйдж
источник
2

Попробуйте "благословения"

Я искал то же самое. Он очень прост в использовании и предлагает инструменты для окраски, укладки и позиционирования в терминале. То, что вам нужно, так же просто, как:

from blessings import Terminal

t = Terminal()

w = t.width
h = t.height

Работает как шарм в Linux. (Я не уверен насчет MacOSX и Windows)

Скачать и документацию здесь

или вы можете установить его с помощью pip:

pip install blessings
Иман Акбари
источник
В настоящее время я бы сказал, попробуйте "благословенный", который является в настоящее время поддерживаемым форком (и улучшением) "благословений".
Зезолло
1

Ответ @ reannual работает хорошо, но есть проблема с ним: os.popen теперь устарела . subprocessМодуль должен использоваться вместо этого, так вот версия @ reannual код , который использует subprocessи непосредственно отвечает на вопрос (давая ширину столбца непосредственно как int:

import subprocess

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

Протестировано на OS X 10.9

rickcnagy
источник
1

Если вы используете Python 3.3 или выше, я бы порекомендовал встроенный, get_terminal_size()как уже рекомендовано. Однако, если вы застряли в более старой версии и хотите простой кроссплатформенный способ сделать это, вы можете использовать asciimatics . Этот пакет поддерживает версии Python до 2.7 и использует опции, аналогичные предложенным выше, чтобы получить текущий размер терминала / консоли.

Просто создайте свой Screenкласс и используйте dimensionsсвойство, чтобы получить высоту и ширину. Доказано, что это работает на Linux, OSX и Windows.

О - и полное раскрытие здесь: я автор, поэтому, пожалуйста, не стесняйтесь открывать новый выпуск, если у вас есть какие-либо проблемы, чтобы заставить это работать.

Питер Бриттен
источник
0

Вот версия, которая должна быть совместима с Linux и Solaris. Основано на сообщениях и комментариях от Madchine . Требуется модуль подпроцесса.

def termize ():
    импорт шлекс, подпроцесс, ре
    output = subprocess.check_output (shlex.split ('/ bin / stty -a'))
    m = re.search ('строки \ D + (? P \ d +); столбцы \ D + (? P \ d +);', выходные данные)
    если М:
        вернуть m.group («строки»), m.group («столбцы»)
    поднять OSError ('Неверный ответ:% s'% (вывод))
>>> termize ()
(«40», «100»)
Деррик Петцольд
источник