Come ottenere la larghezza della finestra della console Linux in Python


264

Esiste un modo in Python per determinare a livello di programmazione la larghezza della console? Intendo il numero di caratteri che si inserisce in una riga senza avvolgimento, non la larghezza in pixel della finestra.

modificare

Alla ricerca di una soluzione che funziona su Linux


Cerca questa risposta per una soluzione più ampia di avere un meccanismo di stampa "dipendente dalle colonne". stackoverflow.com/questions/44129613/…
Riccardo Petraglia,

Risposte:


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

usa il comando 'stty size' che secondo un thread sulla mailing list di Python è ragionevolmente universale su Linux. Apre il comando 'stty size' come file, 'legge' da esso e utilizza una semplice suddivisione in stringhe per separare le coordinate.

A differenza del valore os.environ ["COLUMNS"] (a cui non posso accedere nonostante l'utilizzo di bash come shell standard), anche i dati saranno aggiornati, mentre credo che os.environ ["COLUMNS"] il valore sarebbe valido solo per il momento del lancio dell'interprete python (supponiamo che l'utente abbia ridimensionato la finestra da allora).

(Vedi la risposta di @GringoSuave su come eseguire questa operazione su Python 3.3+)


1
Puoi farlo funzionare anche su Solaris se invece di "size" passi "-a". Ci saranno "righe = Y; colonne = X" nell'output delimitato da punti e virgola.
Joseph Garvin,

5
COLUMNS non viene esportato per impostazione predefinita in Bash, ecco perché os.environ ["COLUMNS"] non funziona.
Kjell Andreassen,

38
rows, columns = subprocess.check_output(['stty', 'size']).split()è un po 'più breve, più il sottoprocesso è il futuro
cdosborn il

7
tput è meglio di stty, poiché stty non può funzionare con PIPE.
liuyang1

5
rows, columns = subprocess.check_output(['stty', 'size']).decode().split()Se vuoi stringhe unicode per la compatibilità py2 / 3
Bryce Guinta

264

Non sono sicuro del motivo per cui si trova nel modulo shutil, ma è arrivato lì in Python 3.3, Interrogando le dimensioni del terminale di output :

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

Un'implementazione di basso livello è nel modulo os. Funziona anche in Windows.

Un backport è ora disponibile per Python 3.2 e versioni precedenti:


25
Questo perché non dovresti più usare 2.7, fai il salto a 3.x ne vale la pena.
anthonyryan1

5
@osirisgothra Molti provider di hosting non supportano ancora python3, quindi alcuni di noi sono costretti a utilizzare python2 per lo sviluppo del back-end. Anche se questo non dovrebbe avere nulla a che fare con la dimensione del terminale ...
Barbabianca

4
@osirisgothra Perché c'è molto codice Python 2 che richiederebbe troppo lavoro per il port. Inoltre, Pypy ha ancora un supporto Python 3 piuttosto scarso.
Antimonio

2
@whitebeard C'è un motivo per cui l'abbonato non può installare Python 3 su un VPS? O nel 2016, le persone utilizzano ancora l'hosting condiviso il cui amministratore non è disposto a installare Python 3? Ad esempio, è stato python3.5installato l' hosting condiviso WebFaction .
Damian Yerrick,

2
+1 per una soluzione che funziona anche quando l'input standard è stato reindirizzato da un file! Con altre soluzioni, stavo ricevendo Inappropriate ioctl for deviceerrori / avvisi o ottenendo il valore di fallback definito di 80.
pawamoy,

65

uso

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

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

EDIT : oh, mi dispiace. Non è un lib lib standard di Python, ecco la fonte di console.py (non so da dove provenga).

Il modulo sembra funzionare in questo modo: controlla se termcapè disponibile, quando sì. Lo usa; se no controlla se il terminale supporta una ioctlchiamata speciale e anche se non funziona, controlla le variabili d'ambiente che alcune shell esportano per quello. Questo probabilmente funzionerà solo su 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
Grazie per la rapida risposta, ma qui ( effbot.org/zone/console-handbook.htm ) dice che "Il modulo Console è attualmente disponibile solo per Windows 95, 98, NT e 2000." Sto cercando una soluzione che funzioni su Linux. Probabilmente non era chiaro dal tag, modificherò la domanda di conseguenza.
Sergey Golovchenko,

2
poiché questo modulo "console" che stai utilizzando non si trova nella libreria standard di Python, dovresti fornire il suo codice sorgente o almeno un link ad esso.
nosklo,

Mi dispiace tanto per quello. In realtà, non conoscevo quel modulo. Ho provato a importare la console e ha funzionato, ho usato console. Sono stati visualizzati <tab> <tab> e getTerminalSize (). Invece di guardare da dove ho già pubblicato una risposta perché sono stato così fortunato della semplicità g
Johannes Weiss,

Potrei guardare un modulo "console" diverso, potresti fornire un link per quello che hai?
Sergey Golovchenko,

4
Oh, e non ammucchiare il codice, ma "cr" è un nome confuso perché implica che la tupla è (cols, righe). In realtà, è il contrario.
Paul Du Bois,

57

Il codice sopra non ha restituito il risultato corretto sul mio Linux perché winsize-struct ha 4 short senza segno, non 2 short con segno:

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 dovrebbero contenere la larghezza e l'altezza dei pixel, ma non lo fanno.


4
Così è come dovrebbe essere fatto; si noti che se si intende stampare sul terminale, è necessario utilizzare '1' come descrittore di file (primo argomento di ioctl), poiché stdin potrebbe essere una pipe o un diverso tty.
mic_e

1
Forse lo 0 dovrebbe essere sostituito confcntl.ioctl(sys.stdin.fileno(), ...
raylu il

4
questa è la risposta migliore - i tuoi utenti saranno felici che non ci sia un sottoprocesso a sorpresa solo per ottenere la durata del termine
zzzeek

4
questa è davvero la risposta più pulita. Penso che dovresti usare stdouto stderrinvece di stdin, però. stdinpotrebbe benissimo essere una pipa. Potresti anche voler aggiungere una riga come if not os.isatty(0): return float("inf").
mic_e,

questo funziona in qualche modo su un terminale Chromebook che non ha funzionalità. +1
HyperNeutrino,

39

Ho cercato e trovato una soluzione per Windows su:

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

e una soluzione per Linux qui.

Quindi ecco una versione che funziona sia su Linux, OS X sia su 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

Mi hai risparmiato il tempo di farlo da solo. Funziona su Linux. Dovrebbe funzionare anche su Windows. Grazie!
Steve V.

30

O è:

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

La shutilfunzione è solo un wrapper attorno a osuno che rileva alcuni errori e imposta un fallback, tuttavia ha un enorme avvertimento: si rompe durante il piping! , che è un affare abbastanza grande.
Per ottenere invece le dimensioni del terminale quando si utilizzano le tubazioni os.get_terminal_size(0).

Il primo argomento 0è un argomento che indica che è necessario utilizzare il descrittore di file stdin anziché lo stdout predefinito. Vogliamo usare stdin perché stdout si stacca da solo quando viene convogliato, il che in questo caso genera un errore.

Ho provato a capire quando avrebbe senso usare stdout invece dell'argomento stdin e non ho idea del perché sia ​​un valore predefinito qui.


3
os.get_terminal_size()fu introdotto in Python 3.3
villapx il

All'inizio ho provato shutil e ha funzionato al primo tentativo. Sto usando Python 3.6+.
Dan

L'uso os.get_terminal_size(0)andrà in crash se si esegue il pipe su stdin. Prova:echo x | python3 -c 'import os; print(os.get_terminal_size(0))'
ehabkost,


6

Sembra che ci siano alcuni problemi con quel codice, Johannes:

  • getTerminalSize deve import os
  • che cos'è env? sembra os.environ.

Inoltre, perché cambiare linese colsprima di tornare? Se TIOCGWINSZed sttyentrambi dicono linesallora cols, io dico di lasciarlo così. Questo mi ha confuso per ben 10 minuti prima di notare l'incoerenza.

Sridhar, non ho riscontrato quell'errore quando ho inviato l'output. Sono abbastanza sicuro che sia stato catturato correttamente nel tentativo-tranne.

Pascal, "HHHH"non funziona sulla mia macchina, ma "hh"funziona. Ho avuto difficoltà a trovare la documentazione per quella funzione. Sembra che dipende dalla piattaforma.

chochem, incorporato.

Ecco la mia versione:

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)

Stavo vagando anche per questo env, ed è davvero env = os.environ, dalla risposta accettata .
sdaau,

6

Molte delle implementazioni di Python 2 qui falliranno se non c'è un terminale di controllo quando si chiama questo script. Puoi controllare sys.stdout.isatty () per determinare se si tratta effettivamente di un terminale, ma ciò escluderà un sacco di casi, quindi credo che il modo più pitonico per capire la dimensione del terminale sia usare il pacchetto curses incorporato.

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

2

Stavo provando la soluzione da qui che chiama stty size:

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

Tuttavia, ciò non è riuscito per me perché stavo lavorando a uno script che prevede input reindirizzati su stdin e sttyin questo caso si lamenterebbe che "stdin non è un terminale".

Sono stato in grado di farlo funzionare in questo modo:

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

2

Prova le "benedizioni"

Stavo cercando la stessa cosa. È molto facile da usare e offre strumenti per colorare, disegnare e posizionare nel terminale. Quello di cui hai bisogno è facile come:

from blessings import Terminal

t = Terminal()

w = t.width
h = t.height

Funziona come un fascino in Linux. (Non sono sicuro di MacOSX e Windows)

Scarica e documentazione qui

oppure puoi installarlo con pip:

pip install blessings

Oggi direi di provare "benedetto" che è attualmente un fork (e potenziamento) di "benedizioni".
Zezollo,

1

La risposta di @ reannual funziona bene, ma c'è un problema: os.popen è ora obsoleta . Il subprocessmodulo dovrebbe essere usato invece, quindi ecco una versione del codice di @ reannual che usa subprocesse risponde direttamente alla domanda (dando la larghezza della colonna direttamente come int:

import subprocess

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

Testato su OS X 10.9


1

Se stai usando Python 3.3 o versioni successive, consiglierei il built-in get_terminal_size()come già raccomandato. Tuttavia, se sei bloccato con una versione precedente e desideri un modo semplice e multipiattaforma per farlo, potresti usare asciimatics . Questo pacchetto supporta le versioni di Python fino alla 2.7 e utilizza opzioni simili a quelle suggerite sopra per ottenere le dimensioni correnti del terminale / console.

Costruisci semplicemente la tua Screenclasse e usa la dimensionsproprietà per ottenere l'altezza e la larghezza. Questo ha dimostrato di funzionare su Linux, OSX e Windows.

Oh, e completa divulgazione qui: io sono l'autore, quindi non esitare ad aprire un nuovo numero se hai problemi a farlo funzionare.


0

Ecco una versione che dovrebbe essere compatibile con Linux e Solaris. Sulla base di messaggi e commenti da madchine . Richiede il modulo di sottoprocesso.

def termsize ():
    import shlex, subprocess, re
    output = subprocess.check_output (shlex.split ('/ bin / stty -a'))
    m = re.search ('righe \ D + (? P \ d +); colonne \ D + (? P \ d +);', output)
    se m:
        return m.group ('file'), m.group ('colonne')
    aumentare OSError ('Risposta errata:% s'% (output))
>>> termsize ()
('40', '100')
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.