Cómo obtener el ancho de la ventana de la consola de Linux en Python

264

¿Hay alguna manera en Python para determinar mediante programación el ancho de la consola? Me refiero a la cantidad de caracteres que cabe en una línea sin ajuste, no al ancho de píxeles de la ventana.

Editar

Buscando una solución que funcione en Linux

Sergey Golovchenko
fuente
Mire esta respuesta para una solución más extensa para tener un mecanismo de impresión "dependiente de columnas". stackoverflow.com/questions/44129613/…
Riccardo Petraglia

Respuestas:

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

usa el comando 'stty size' que, según un hilo en la lista de correo de Python, es razonablemente universal en Linux. Abre el comando 'tamaño stty' como un archivo, 'lee' de él y usa una división de cadena simple para separar las coordenadas.

A diferencia del valor os.environ ["COLUMNS"] (al que no puedo acceder a pesar de usar bash como mi shell estándar), los datos también estarán actualizados, mientras que creo que os.environ ["COLUMNS"] El valor solo sería válido para el momento del lanzamiento del intérprete de Python (supongamos que el usuario ha cambiado el tamaño de la ventana desde entonces).

(Vea la respuesta de @GringoSuave sobre cómo hacer esto en Python 3.3+)

brokkr
fuente
1
También puede hacer que esto funcione en Solaris si en lugar de "tamaño" pasa "-a". Habrá "filas = Y; columnas = X" en la salida delimitada por punto y coma.
Joseph Garvin
55
COLUMNS no se exporta por defecto en Bash, por eso os.environ ["COLUMNS"] no funciona.
Kjell Andreassen
38
rows, columns = subprocess.check_output(['stty', 'size']).split()es un poco más corto, más subproceso es el futuro
cdosborn
77
tput es mejor que stty, ya que stty no puede funcionar con PIPE.
liuyang1
55
rows, columns = subprocess.check_output(['stty', 'size']).decode().split()Si desea cadenas unicode para compatibilidad con py2 / 3
Bryce Guinta
264

No estoy seguro de por qué está en el módulo shutil, pero aterrizó allí en Python 3.3, consultando el tamaño del terminal de salida :

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

Una implementación de bajo nivel está en el módulo os. También funciona en Windows.

Un backport ahora está disponible para Python 3.2 y siguientes:

Gringo Suave
fuente
25
Eso es porque ya no deberías usar 2.7, haz el salto a 3.x vale la pena.
anthonyryan1
55
@osirisgothra Muchos proveedores de alojamiento aún no son compatibles con python3, por lo que algunos de nosotros estamos obligados a usar python2 para el desarrollo de back-end. A pesar de que no debería tener nada que ver con conseguir el tamaño del terminal ...
Barbablanca
44
@osirisgothra Porque hay mucho código de Python 2 que requeriría demasiado trabajo para portar. Además, Pypy todavía tiene un soporte bastante pobre para Python 3.
Antimonio
2
@whitebeard ¿Hay alguna razón por la que el suscriptor no pueda instalar Python 3 en un VPS? ¿O en 2016, la gente todavía usa hosting compartido cuyo administrador no está dispuesto a instalar Python 3? Por ejemplo, el alojamiento compartido de WebFaction se ha python3.5instalado.
Damian Yerrick
2
¡+1 para una solución que también funciona cuando la entrada estándar se ha redirigido desde un archivo! Con otras soluciones, recibía Inappropriate ioctl for deviceerrores / advertencias o el valor de recuperación definido de 80.
pawamoy
65

utilizar

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

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

EDITAR : oh, lo siento. Esa no es una versión estándar de Python, aquí está la fuente de console.py (no sé de dónde es).

El módulo parece funcionar así: comprueba si termcapestá disponible, en caso afirmativo. Utiliza eso; si no, verifica si el terminal admite una ioctlllamada especial y eso tampoco funciona, verifica las variables de entorno que algunos shells exportan para eso. Esto probablemente funcionará solo en 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
fuente
55
Gracias por su rápida respuesta, pero aquí ( effbot.org/zone/console-handbook.htm ) dice que "El módulo de consola actualmente solo está disponible para Windows 95, 98, NT y 2000". Estoy buscando una solución que funcione en Linux. Probablemente no estaba claro en la etiqueta, editaré la pregunta en consecuencia.
Sergey Golovchenko
2
Dado que este módulo de "consola" que está utilizando no está en la biblioteca estándar de Python, debe proporcionar su código fuente o al menos un enlace.
nosklo
Lo siento mucho por eso. De hecho, no conocía ese módulo. Intenté importar la consola y funcionó, utilicé la consola. <tab> <tab> y getTerminalSize () aparecieron. En lugar de mirar de dónde es, ya publiqué una respuesta porque tuve mucha suerte con la simplicidad g
Johannes Weiss
Podría estar buscando un módulo de "consola" diferente, ¿podría proporcionar un enlace para el que tiene?
Sergey Golovchenko
44
Ah, y no apilar el código, pero "cr" es un nombre confuso porque implica que la tupla es (cols, filas). En realidad, es lo contrario.
Paul Du Bois
57

El código anterior no devolvió el resultado correcto en mi Linux porque winsize-struct tiene 4 cortos sin firmar, no 2 cortos firmados:

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 y HP deben contener ancho y alto de píxeles, pero no lo hacen.

pascal
fuente
44
Asi es como debería de hacerse; tenga en cuenta que si tiene la intención de imprimir en el terminal, debe usar '1' como descriptor de archivo (primer argumento de ioctl), ya que stdin podría ser una tubería o un tty diferente.
mic_e
1
Quizás el 0 debería ser reemplazado porfcntl.ioctl(sys.stdin.fileno(), ...
raylu
44
esta es la mejor respuesta - los usuarios van a estar contentos de que no hay un subproceso sorpresa ocurriendo sólo para obtener la anchura plazo
zzzeek
44
Esta es de hecho la respuesta más clara. Sin embargo, creo que deberías usar stdouto en stderrlugar de stdin. stdinbien podría ser una pipa. También es posible que desee agregar una línea como if not os.isatty(0): return float("inf").
mic_e
esto de alguna manera funciona en un terminal de Chromebook que no tiene ninguna funcionalidad. +1
HyperNeutrino
39

Busqué y encontré una solución para Windows en:

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

y una solución para Linux aquí.

Así que aquí hay una versión que funciona tanto en Linux, OS X y 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
fuente
Me ahorraste el tiempo de hacer esto yo mismo. Funciona en Linux. Debería funcionar también en Windows. ¡Gracias!
Steve V.
30

Es cualquiera:

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

La shutilfunción es solo una envoltura alrededor de osuna que detecta algunos errores y configura un respaldo, sin embargo, tiene una gran advertencia: ¡ se rompe al conectar tuberías! , que es un gran negocio.
Para obtener el tamaño de la terminal al utilizar tuberías, en su os.get_terminal_size(0)lugar.

El primer argumento 0es un argumento que indica que se debe usar el descriptor de archivo stdin en lugar del stdout predeterminado. Queremos usar stdin porque stdout se separa cuando se canaliza, lo que en este caso genera un error.

He intentado averiguar cuándo tendría sentido usar stdout en lugar de argumento stdin y no tengo idea de por qué es un valor predeterminado aquí.

Granitosaurio
fuente
3
os.get_terminal_size()se introdujo en Python 3.3
villapx
Al principio probé shutil, y funcionó el primer intento. Estoy usando Python 3.6+.
Dan
El uso os.get_terminal_size(0)se bloqueará si se canaliza a stdin. Prueba:echo x | python3 -c 'import os; print(os.get_terminal_size(0))'
ehabkost
19

Comenzando en Python 3.3 es sencillo: 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
fuente
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 es una copia cercana de una respuesta de un año anterior dada anteriormente.
Gringo Suave
6

Parece que hay algunos problemas con ese código, Johannes:

  • getTerminalSize necesita import os
  • lo que es env? Aspecto del producto os.environ.

Además, ¿por qué cambiar linesy colsantes de regresar? Si TIOCGWINSZy sttyambos dicen linesentonces cols, yo digo que lo dejes así. Esto me confundió durante unos 10 minutos antes de notar la inconsistencia.

Sridhar, no recibí ese error cuando canalicé la salida. Estoy bastante seguro de que está siendo atrapado correctamente en el try-except.

pascal, "HHHH"no funciona en mi máquina, pero "hh"sí. Tuve problemas para encontrar documentación para esa función. Parece que depende de la plataforma.

chochem, incorporado.

Aquí está mi versión:

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
fuente
Estaba deambulando por el envtambién, y de hecho env = os.environ, es por respuesta aceptada .
sdaau
6

Muchas de las implementaciones de Python 2 fallarán aquí si no hay un terminal de control cuando se llama a este script. Puede verificar sys.stdout.isatty () para determinar si esto es realmente un terminal, pero eso excluirá un montón de casos, por lo que creo que la forma más pitónica de averiguar el tamaño del terminal es usar el paquete de maldiciones incorporado.

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

Estaba probando la solución desde aquí que dice stty size:

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

Sin embargo, esto falló para mí porque estaba trabajando en un script que espera una entrada redirigida en stdin, y me sttyquejaría de que "stdin no es una terminal" en ese caso.

Pude hacer que funcione así:

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

Prueba las "bendiciones"

Estaba buscando lo mismo. Es muy fácil de usar y ofrece herramientas para colorear, peinar y posicionar en el terminal. Lo que necesitas es tan fácil como:

from blessings import Terminal

t = Terminal()

w = t.width
h = t.height

Funciona como un encanto en Linux. (No estoy seguro acerca de MacOSX y Windows)

Descarga y documentación aquí

o puedes instalarlo con pip:

pip install blessings
Iman Akbari
fuente
Hoy en día diría que intentes "bendecido", que es una bifurcación (y mejora) de "bendiciones" que se mantiene actualmente.
zezollo
1

La respuesta de @ reannual funciona bien, pero hay un problema: os.popen ahora está en desuso . El subprocessmódulo debe usarse en su lugar, así que aquí hay una versión del código de @ reannual que usa subprocessy responde directamente a la pregunta (dando el ancho de columna directamente como int:

import subprocess

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

Probado en OS X 10.9

rickcnagy
fuente
1

Si está utilizando Python 3.3 o superior, recomendaría el incorporado get_terminal_size()como ya se recomienda. Sin embargo, si está atascado con una versión anterior y desea una forma simple y multiplataforma de hacerlo, puede usar asciimáticos . Este paquete admite versiones de Python a 2.7 y utiliza opciones similares a las sugeridas anteriormente para obtener el tamaño actual de terminal / consola.

Simplemente construya su Screenclase y use la dimensionspropiedad para obtener el alto y el ancho. Se ha demostrado que esto funciona en Linux, OSX y Windows.

Ah, y divulgación completa aquí: soy el autor, así que siéntase libre de abrir un nuevo problema si tiene algún problema para que esto funcione.

Peter Brittain
fuente
0

Aquí hay una versión que debería ser compatible con Linux y Solaris. Basado en los mensajes y comentarios de madchine . Requiere el módulo de subproceso.

def termsize ():
    importar shlex, subproceso, re
    output = subprocess.check_output (shlex.split ('/ bin / stty -a'))
    m = re.search ('filas \ D + (? P \ d +); columnas \ D + (? P \ d +);', salida)
    si m:
        return m.group ('filas'), m.group ('columnas')
    raise OSError ('Mala respuesta:% s'% (salida))
>>> termsize ()
('40', '100')
Derrick Petzold
fuente