Como obter a largura da janela do console Linux em Python

264

Existe uma maneira em python para determinar programaticamente a largura do console? Quero dizer o número de caracteres que cabe em uma linha sem quebra automática, não a largura de pixel da janela.

Editar

Procurando uma solução que funcione no Linux

Sergey Golovchenko
fonte
Procure nesta resposta uma solução mais extensa para ter um mecanismo de impressão "dependente de colunas". stackoverflow.com/questions/44129613/…
Riccardo Petraglia

Respostas:

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

usa o comando 'stty size' que de acordo com um thread na lista de discussão python é razoavelmente universal no linux. Ele abre o comando 'stty size' como um arquivo, 'lê' a partir dele e usa uma divisão simples de string para separar as coordenadas.

Diferente do valor os.environ ["COLUMNS"] (que não consigo acessar, apesar de usar o bash como meu shell padrão), os dados também estarão atualizados, enquanto acredito que o os.environ ["COLUMNS"] O valor seria válido apenas durante o lançamento do interpretador python (suponha que o usuário redimensione a janela desde então).

(Veja a resposta de @GringoSuave sobre como fazer isso no python 3.3+)

brokkr
fonte
1
Você também pode fazer isso funcionar no Solaris se, em vez de "tamanho", passar "-a". Haverá "linhas = Y; colunas = X" na saída delimitada por ponto e vírgula.
Joseph Garvin
5
COLUMNS não é exportado por padrão no Bash, é por isso que os.environ ["COLUMNS"] não funciona.
Kjell Andreassen
38
rows, columns = subprocess.check_output(['stty', 'size']).split()é um pouco mais curto, mais subprocesso é o futuro
cdosborn
7
tput é melhor que stty, pois stty não pode funcionar com o PIPE.
liuyang1
5
rows, columns = subprocess.check_output(['stty', 'size']).decode().split()Se você quiser strings unicode para compatibilidade PY2 / 3
Bryce Guinta
264

Não sei por que ele está no módulo shutil, mas foi parar no Python 3.3, Consultando o tamanho do terminal de saída :

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

Uma implementação de baixo nível está no módulo os. Também funciona no Windows.

Um backport está agora disponível para Python 3.2 e abaixo:

Gringo Suave
fonte
25
Isso porque você não deve mais usar o 2.7, faça o salto para 3.x e vale a pena.
precisa
5
@osirisgothra Muitos provedores de hospedagem ainda não oferecem suporte ao python3; portanto, alguns de nós são forçados a usar o python2 para o desenvolvimento de back-end. No entanto, que deve ter nada a ver com a obtenção de tamanho do terminal ...
Barba Branca
4
@osirisgothra Porque há muito código Python 2 que exigiria muito trabalho para ser portado. Além disso, o Pypy ainda possui um suporte pobre ao Python 3.
Antimony
2
@whitebeard Existe algum motivo para o assinante não conseguir instalar o Python 3 em um VPS? Ou em 2016, as pessoas ainda estão usando hospedagem compartilhada cujo administrador não está disposto a instalar o Python 3? Por exemplo, a hospedagem compartilhada do WebFaction foi python3.5instalada.
Damian Yerrick
2
+1 para uma solução que também funciona quando a entrada padrão foi redirecionada de um arquivo! Com outras soluções, ou eu estava recebendo Inappropriate ioctl for deviceerros / avisos, ou se o valor fallback definido de 80.
pawamoy
65

usar

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

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

EDIT : oh, me desculpe. Essa não é uma lib padrão do python, aqui está a fonte do console.py (não sei de onde é).

O módulo parece funcionar assim: verifica se termcapestá disponível, quando sim. Ele usa isso; caso contrário, verifica se o terminal suporta uma ioctlchamada especial e também não funciona, verifica as variáveis ​​de ambiente que alguns shells exportam para isso. Provavelmente isso funcionará apenas no 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
fonte
5
Obrigado pela resposta rápida, mas aqui ( effbot.org/zone/console-handbook.htm ) diz que "No momento, o módulo Console está disponível apenas para Windows 95, 98, NT e 2000". Estou procurando uma solução que funcione no Linux. Provavelmente não ficou claro na tag, editarei a pergunta de acordo.
Sergey Golovchenko
2
como esse módulo "console" que você está usando não está na biblioteca python padrão, você deve fornecer o código-fonte ou pelo menos um link para ele.
19339 nosklo
Sinto muito por isso. Na verdade, eu não conhecia esse módulo. Tentei importar console e funcionou, usei console. <tab> <tab> e getTerminalSize () apareceram. Em vez de olhar de onde vem I já postou uma resposta porque eu estava tão sorte da simplicidade g
Johannes Weiss
Talvez eu esteja procurando um módulo "console" diferente, você poderia fornecer um link para o que possui?
Sergey Golovchenko
4
Ah, e não para acumular no código, mas "cr" é um nome confuso porque implica que a tupla é (colunas, linhas). Na realidade, é o contrário.
Paul Du Bois
57

O código acima não retornou o resultado correto no meu linux porque o winsize-struct possui 4 shorts não assinados, não 2 shorts assinados:

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 e hp devem conter largura e altura de pixel, mas não.

pascal
fonte
4
É assim que deve ser feito; note que se você pretende imprimir no terminal, use '1' como o descritor de arquivo (primeiro argumento do ioctl), pois stdin pode ser um pipe ou algum tty diferente.
22813 mic_e
1
Talvez o 0 deva ser substituído porfcntl.ioctl(sys.stdin.fileno(), ...
raylu
4
esta é a melhor resposta - seus usuários será feliz que não há um subprocesso surpresa acontecendo apenas para obter largura termo
zzzeek
4
esta é realmente a resposta mais limpa. Eu acho que você deveria usar stdoutou em stderrvez de stdin, no entanto. stdinpode muito bem ser um cano. Você também pode adicionar uma linha como if not os.isatty(0): return float("inf").
mic_e
isso funciona de alguma forma em um terminal do chromebook que não possui funcionalidade. 1
HyperNeutrino 15/11
39

Eu procurei e encontrei uma solução para o Windows em:

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

e uma solução para linux aqui.

Então, aqui está uma versão que funciona tanto em linux, os x quanto 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
fonte
Você me salvou o tempo de fazer isso sozinho. Funciona no Linux. Também deve funcionar no Windows. Obrigado!
Steve V.
30

Também é:

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

A shutilfunção é apenas um invólucro em torno de osum que detecta alguns erros e configura um fallback, no entanto, possui uma grande ressalva - ela quebra ao canalizar! , o que é um grande negócio.
Para obter o tamanho do terminal ao usar a tubulação os.get_terminal_size(0).

Primeiro argumento 0 é um argumento indicando que o descritor de arquivo stdin deve ser usado em vez do stdout padrão. Queremos usar o stdin porque o stdout se desconecta quando está sendo canalizado, o que nesse caso gera um erro.

Eu tentei descobrir quando faria sentido usar o argumento stdout em vez do argumento stdin e não faço idéia do por que é um padrão aqui.

Granitosaurus
fonte
3
os.get_terminal_size()foi introduzido no Python 3.3
villapx 13/03/17
Eu tentei shutil no começo, e funcionou na primeira tentativa. Estou usando o Python 3.6+.
Dan
O uso os.get_terminal_size(0)falhará se você usar o stdin. Tente:echo x | python3 -c 'import os; print(os.get_terminal_size(0))'
ehabkost 28/11/19
19

A partir do Python 3.3, é simples: 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
fonte
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
Esta é uma cópia aproximada de uma resposta do ano anterior dada acima.
Gringo Suave
6

Parece que há alguns problemas com esse código, Johannes:

  • getTerminalSize precisa de import os
  • o que é env? parece os.environ.

Além disso, por que mudar linese colsantes de retornar? Se TIOCGWINSZe os sttydois dizem linesentão cols, digo assim. Isso me confundiu por uns bons 10 minutos antes de eu notar a inconsistência.

Sridhar, não recebi esse erro quando canalizei a saída. Tenho certeza de que está sendo capturado corretamente na tentativa, exceto.

pascal, "HHHH"não funciona na minha máquina, mas "hh"funciona. Eu tive problemas para encontrar documentação para essa função. Parece que depende da plataforma.

chochem, incorporado.

Aqui está a minha versão:

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
fonte
Eu estava vagando sobre o envtambém, e é de facto env = os.environ, de resposta aceita .
Sdaau
6

Muitas das implementações do Python 2 aqui falharão se não houver um terminal de controle quando você chamar esse script. Você pode verificar sys.stdout.isatty () para determinar se esse é realmente um terminal, mas isso excluirá muitos casos, portanto, acredito que a maneira mais pitônica de descobrir o tamanho do terminal é usar o pacote de maldições embutido.

import curses
w = curses.initscr()
height, width = w.getmaxyx()
Wonton
fonte
2

Eu estava tentando a solução daqui que chama stty size:

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

No entanto, isso falhou para mim porque eu estava trabalhando em um script que espera uma entrada redirecionada no stdin e sttyqueixaria-me de que "stdin não é um terminal" nesse caso.

Consegui fazê-lo funcionar assim:

with open('/dev/tty') as tty:
    height, width = subprocess.check_output(['stty', 'size'], stdin=tty).split()
Marc Liyanage
fonte
2

Tente "bênçãos"

Eu estava procurando a mesma coisa. É muito fácil de usar e oferece ferramentas para colorir, modelar e posicionar no terminal. O que você precisa é tão fácil quanto:

from blessings import Terminal

t = Terminal()

w = t.width
h = t.height

Funciona como um encanto no Linux. (Não tenho certeza sobre o MacOSX e o Windows)

Download e documentação aqui

ou você pode instalá-lo com o pip:

pip install blessings
Iman Akbari
fonte
Atualmente, eu diria que tente "abençoado", que atualmente é um garfo (e aprimoramento) de "bênçãos".
zezollo
1

A resposta do @ reannual funciona bem, mas há um problema: os.popen agora está obsoleto . O subprocessmódulo deve ser usado, então aqui está uma versão do código do @ reannual que usa subprocesse responde diretamente à pergunta (fornecendo a largura da coluna diretamente como int:

import subprocess

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

Testado no OS X 10.9

rickcnagy
fonte
1

Se você estiver usando o Python 3.3 ou superior, recomendo o built-in get_terminal_size()como já recomendado. No entanto, se você está preso a uma versão mais antiga e deseja uma maneira simples e multiplataforma de fazer isso, pode usar asciimatics . Este pacote suporta versões do Python de volta para 2.7 e usa opções semelhantes às sugeridas acima para obter o tamanho atual do terminal / console.

Simplesmente construa sua Screenclasse e use a dimensionspropriedade para obter a altura e a largura. Está provado que funciona no Linux, OSX e Windows.

Ah - e divulgação completa aqui: eu sou o autor, por isso, sinta-se à vontade para abrir uma nova edição, se você tiver algum problema para fazer isso funcionar.

Peter Brittain
fonte
0

Aqui está uma versão que deve ser compatível com Linux e Solaris. Baseado nos posts e comentários de madchine . Requer o módulo de subprocesso.

def termsize ():
    importar shlex, subprocesso, re
    saída = subprocess.check_output (shlex.split ('/ bin / stty -a'))
    m = re.search ('linhas \ D + (? P \ d +); colunas \ D + (? P \ d +);', saída)
    se m:
        retornar m.group ('linhas'), m.group ('colunas')
    aumentar OSError ('Resposta incorreta:% s'% (saída))
>>> termosize ()
('40', '100')
Derrick Petzold
fonte