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


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+)


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:


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])

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.


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

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í.


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


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)

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()

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()

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

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


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.


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')
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.