Comment obtenir la largeur de la fenêtre de la console Linux en Python

264

Existe-t-il un moyen en python de déterminer par programme la largeur de la console? Je veux dire le nombre de caractères qui tient sur une ligne sans habillage, pas la largeur en pixels de la fenêtre.

Éditer

Vous recherchez une solution qui fonctionne sous Linux

Sergey Golovchenko
la source
Recherchez cette réponse pour une solution plus complète pour avoir un mécanisme d'impression "dépendant des colonnes". stackoverflow.com/questions/44129613/…
Riccardo Petraglia

Réponses:

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

utilise la commande 'stty size' qui, selon un thread de la liste de diffusion python, est raisonnablement universelle sur linux. Il ouvre la commande «stty size» sous forme de fichier, «le lit» et utilise une simple chaîne de partage pour séparer les coordonnées.

Contrairement à la valeur os.environ ["COLUMNS"] (à laquelle je ne peux pas accéder malgré l'utilisation de bash comme shell standard), les données seront également à jour alors que je crois que os.environ ["COLUMNS"] La valeur ne serait valide que pour le lancement de l'interpréteur Python (supposons que l'utilisateur ait redimensionné la fenêtre depuis).

(Voir la réponse de @GringoSuave sur la façon de le faire sur python 3.3+)

brokkr
la source
1
Vous pouvez également faire fonctionner ceci sur Solaris si au lieu de "taille" vous passez "-a". Il y aura "lignes = Y; colonnes = X" dans la sortie délimitée par des points-virgules.
Joseph Garvin
5
COLUMNS n'est pas exporté par défaut dans Bash, c'est pourquoi os.environ ["COLUMNS"] ne fonctionne pas.
Kjell Andreassen
38
rows, columns = subprocess.check_output(['stty', 'size']).split()est un peu plus court, et le sous-processus est l'avenir
cdosborn
7
tput est meilleur que stty, car stty ne peut pas fonctionner avec PIPE.
liuyang1
5
rows, columns = subprocess.check_output(['stty', 'size']).decode().split()Si vous voulez des chaînes Unicode pour la compatibilité py2 / 3
Bryce Guinta
264

Je ne sais pas pourquoi il est dans le module shutil, mais il a atterri là-bas en Python 3.3, Interrogation de la taille du terminal de sortie :

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

Une implémentation de bas niveau se trouve dans le module os. Fonctionne également sous Windows.

Un backport est désormais disponible pour Python 3.2 et versions antérieures:

Gringo Suave
la source
25
C'est parce que vous ne devriez plus utiliser 2.7, faites le saut à 3.x ça vaut le coup.
anthonyryan1
5
@osirisgothra De nombreux hébergeurs ne prennent pas encore en charge python3, donc certains d'entre nous sont obligés d'utiliser python2 pour le développement back-end. Bien que cela n'ait rien à voir avec l'obtention de la taille du terminal ...
whitebeard
4
@osirisgothra Parce qu'il y a beaucoup de code Python 2 qui demanderait trop de travail à porter. De plus, Pypy a toujours un support Python 3 plutôt médiocre.
Antimony
2
@whitebeard Y a-t-il une raison pour laquelle l'abonné ne peut pas installer Python 3 sur un VPS? Ou en 2016, les gens utilisent-ils toujours l'hébergement partagé dont l'administrateur ne veut pas installer Python 3? Par exemple, l'hébergement partagé WebFaction est python3.5installé.
Damian Yerrick
2
+1 pour une solution qui fonctionne également lorsque l'entrée standard a été redirigée depuis un fichier! Avec d'autres solutions, je Inappropriate ioctl for device
recevais
65

utilisation

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

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

EDIT : oh, je suis désolé. Ce n'est pas une bibliothèque standard python, voici la source de console.py (je ne sais pas d'où il vient).

Le module semble fonctionner comme ça: il vérifie s'il termcapest disponible, quand oui. Il utilise cela; si non, il vérifie si le terminal prend en charge un ioctlappel spécial et cela ne fonctionne pas non plus, il vérifie les variables d'environnement que certains shells exportent pour cela. Cela ne fonctionnera probablement que sous 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
la source
5
Merci pour la réponse rapide, mais ici ( effbot.org/zone/console-handbook.htm ), il est dit que "Le module Console n'est actuellement disponible que pour Windows 95, 98, NT et 2000." Je recherche une solution qui fonctionne sous Linux. Ce n'était probablement pas clair à partir de la balise, je modifierai la question en conséquence.
Sergey Golovchenko
2
puisque ce module "console" que vous utilisez n'est pas sur la bibliothèque python standard, vous devez fournir son code source ou au moins un lien vers celui-ci.
nosklo le
Je suis vraiment désolé pour ça. En fait, je ne connaissais pas ce module. J'ai essayé la console d'importation et cela a fonctionné, j'ai utilisé la console. <tab> <tab> et getTerminalSize () sont apparus. Au lieu de chercher d'où ça vient, j'ai déjà posté une réponse parce que j'étais tellement chanceux de la simplicité g
Johannes Weiss
Je regarde peut-être un module "console" différent, pourriez-vous s'il vous plaît fournir un lien pour celui que vous avez?
Sergey Golovchenko
4
Oh, et pas pour empiler sur le code, mais "cr" est un nom déroutant car il implique que le tuple est (cols, lignes). En réalité, c'est l'inverse.
Paul Du Bois
57

Le code ci-dessus n'a pas retourné le résultat correct sur mon linux car winsize-struct a 4 shorts non signés, pas 2 shorts signés:

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 et hp doivent contenir la largeur et la hauteur des pixels, mais pas.

pascal
la source
4
Voilà comment cela devrait être fait; notez que si vous avez l'intention d'imprimer sur le terminal, vous devez utiliser '1' comme descripteur de fichier (premier argument d'ioctl), car stdin peut être un tube ou un tty différent.
mic_e
1
Peut-être que le 0 devrait être remplacé parfcntl.ioctl(sys.stdin.fileno(), ...
raylu
4
c'est la meilleure réponse - vos utilisateurs seront heureux qu'il n'y ait pas de sous-processus surprise juste pour obtenir la durée du terme
zzzeek
4
c'est en effet la réponse la plus claire. Je pense que vous devriez utiliser stdoutou stderrau lieu de stdin, cependant. stdinpourrait très bien être une pipe. Vous pouvez également ajouter une ligne telle que if not os.isatty(0): return float("inf").
mic_e
cela fonctionne en quelque sorte sur un terminal Chromebook qui n'a pas de fonctionnalité. +1
HyperNeutrino
39

J'ai cherché et trouvé une solution pour Windows à:

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

et une solution pour linux ici.

Voici donc une version qui fonctionne à la fois sur linux, os x et 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
la source
Vous m'avez sauvé le temps de le faire moi-même. Fonctionne sous Linux. Devrait également fonctionner sous Windows. Merci!
Steve V.
30

C'est soit:

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

La shutilfonction est juste un wrapper autour de oscelui qui détecte certaines erreurs et configure une solution de repli, mais il a une énorme mise en garde - il se casse lors de la tuyauterie! , ce qui est assez énorme.
Pour obtenir la taille du terminal lors de l'utilisation de la tuyauterieos.get_terminal_size(0) place.

Premier argument 0 est un argument indiquant que le descripteur de fichier stdin doit être utilisé à la place de stdout par défaut. Nous voulons utiliser stdin car stdout se détache quand il est canalisé, ce qui dans ce cas soulève une erreur.

J'ai essayé de comprendre à quel moment il serait judicieux d'utiliser stdout au lieu de l'argument stdin et je n'ai aucune idée pourquoi c'est un défaut ici.

Granitosaurus
la source
3
os.get_terminal_size()a été introduit dans Python 3.3
villapx
J'ai essayé shutil au début, et cela a fonctionné du premier coup. J'utilise Python 3.6+.
Dan
L'utilisation os.get_terminal_size(0)plantera si vous dirigez vers stdin. Essayez:echo x | python3 -c 'import os; print(os.get_terminal_size(0))'
ehabkost
19

À partir de Python 3.3, c'est simple: 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
la source
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
Ceci est une copie proche d'une réponse d'un an donnée ci-dessus.
Gringo Suave
6

Il semble qu'il y ait des problèmes avec ce code, Johannes:

  • getTerminalSize a besoin de import os
  • c'est quoi env? ressemble os.environ.

Aussi, pourquoi changer lineset colsavant de revenir? Si TIOCGWINSZet les sttydeux disent linesalors cols, je dis de laisser les choses de cette façon. Cela m'a dérouté pendant 10 bonnes minutes avant de remarquer l'incohérence.

Sridhar, je n'ai pas eu cette erreur lorsque j'ai canalisé la sortie. Je suis à peu près sûr qu'il est correctement pris dans l'essai, sauf.

pascal, "HHHH"ne fonctionne pas sur ma machine, mais "hh"fonctionne. J'ai eu du mal à trouver de la documentation pour cette fonction. Il semble que cela dépend de la plate-forme.

chochem, incorporé.

Voici ma version:

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
la source
Je me promenais aussi sur le sujet env, et c'est en effet env = os.environde la réponse acceptée .
sdaau
6

De nombreuses implémentations Python 2 échoueront ici s'il n'y a pas de terminal de contrôle lorsque vous appelez ce script. Vous pouvez vérifier sys.stdout.isatty () pour déterminer s'il s'agit en fait d'un terminal, mais cela exclura un tas de cas, donc je pense que la façon la plus pythonique de déterminer la taille du terminal est d'utiliser le package curses intégré.

import curses
w = curses.initscr()
height, width = w.getmaxyx()
wonton
la source
2

J'essayais la solution d'ici qui appelle à stty size:

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

Cependant, cela a échoué pour moi car je travaillais sur un script qui attend une entrée redirigée sur stdin, et stty je me plaindrais que "stdin n'est pas un terminal" dans ce cas.

J'ai pu le faire fonctionner comme ceci:

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

Essayez les "bénédictions"

Je cherchais exactement la même chose. Il est très simple d'utilisation et propose des outils de coloration, de mise en plis et de positionnement dans le terminal. Ce dont vous avez besoin est aussi simple que:

from blessings import Terminal

t = Terminal()

w = t.width
h = t.height

Fonctionne comme un charme sous Linux. (Je ne suis pas sûr de MacOSX et Windows)

Téléchargement et documentation ici

ou vous pouvez l'installer avec pip:

pip install blessings
Iman Akbari
la source
De nos jours, je dirais essayer "béni" qui est une fourchette (et une amélioration) actuellement maintenue de "bénédictions".
zezollo
1

La réponse de @ reannual fonctionne bien, mais elle a un problème: elle os.popen est désormais obsolète . Le subprocessmodule doit être utilisé à la place, alors voici une version du code de @ reannual qui utilise subprocesset répond directement à la question (en donnant directement la largeur de la colonne comme int:

import subprocess

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

Testé sur OS X 10.9

rickcnagy
la source
1

Si vous utilisez Python 3.3 ou supérieur, je recommanderais le intégré get_terminal_size()comme déjà recommandé. Cependant, si vous êtes bloqué avec une version plus ancienne et que vous voulez un moyen simple et multiplateforme de le faire, vous pouvez utiliser des asciimatics . Ce package prend en charge les versions de Python de retour à 2.7 et utilise des options similaires à celles suggérées ci-dessus pour obtenir la taille actuelle du terminal / console.

Construisez simplement votre Screenclasse et utilisez la dimensionspropriété pour obtenir la hauteur et la largeur. Il a été prouvé que cela fonctionne sous Linux, OSX et Windows.

Oh - et divulgation complète ici: je suis l'auteur, alors n'hésitez pas à ouvrir un nouveau problème si vous rencontrez des problèmes pour que cela fonctionne.

Peter Brittain
la source
0

Voici une version qui devrait être compatible Linux et Solaris. Basé sur les publications et les commentaires de madchine . Nécessite le module de sous-processus.

def termsize ():
    import shlex, sous-processus, re
    sortie = subprocess.check_output (shlex.split ('/ bin / stty -a'))
    m = re.search ('lignes \ D + (? P \ d +); colonnes \ D + (? P \ d +);', sortie)
    si m:
        renvoyer m.group («lignes»), m.group («colonnes»)
    lever OSError ('Mauvaise réponse:% s'% (sortie))
>>> termsize ()
(«40», «100»)
Derrick Petzold
la source