Cara mendapatkan lebar jendela konsol Linux dengan Python

264

Apakah ada cara dalam python untuk menentukan lebar konsol secara terprogram? Maksud saya jumlah karakter yang cocok dalam satu baris tanpa pembungkus, bukan lebar piksel jendela.

Edit

Mencari solusi yang berfungsi di Linux

Sergey Golovchenko
sumber
Lihat jawaban ini untuk solusi yang lebih luas untuk memiliki mekanisme pencetakan "tergantung kolom". stackoverflow.com/questions/44129613/...
Riccardo Petraglia

Jawaban:

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

menggunakan perintah 'stty size' yang menurut utas pada milis python cukup universal di linux. Ini membuka perintah 'stty size' sebagai file, 'membaca' dari itu, dan menggunakan split string sederhana untuk memisahkan koordinat.

Tidak seperti nilai os.environ ["COLUMNS"] (yang saya tidak dapat mengakses meskipun menggunakan bash sebagai shell standar saya), data juga akan diperbarui sedangkan saya percaya os.environment ["COLUMNS"] nilai hanya akan valid untuk waktu peluncuran interpreter python (misalkan pengguna mengubah ukuran jendela sejak itu).

(Lihat jawaban oleh @GringoSuave tentang cara melakukan ini di python 3.3+)

brokkr
sumber
1
Anda bisa membuat ini berfungsi pada Solaris juga jika alih-alih "ukuran" Anda lulus "-a". Akan ada "rows = Y; kolom = X" di output dipisahkan titik koma.
Joseph Garvin
5
COLUMNS tidak diekspor secara default di Bash, itu sebabnya os.environ ["COLUMNS"] tidak berfungsi.
Kjell Andreassen
38
rows, columns = subprocess.check_output(['stty', 'size']).split()sedikit lebih pendek, ditambah subproses adalah masa depan
cdosborn
7
tput lebih baik daripada stty, karena stty tidak dapat bekerja dengan PIPE.
liuyang1
5
rows, columns = subprocess.check_output(['stty', 'size']).decode().split()Jika Anda ingin string unicode untuk kompatibilitas py2 / 3
Bryce Guinta
264

Tidak yakin mengapa itu ada dalam modul shutil, tetapi ia mendarat di Python 3.3, menanyakan ukuran terminal keluaran :

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

Implementasi tingkat rendah ada di modul os. Juga berfungsi di Windows.

Backport sekarang tersedia untuk Python 3.2 dan di bawah:

Gringo Suave
sumber
25
Itu karena Anda seharusnya tidak menggunakan 2.7 lagi, lakukan lompatan ke 3.x itu sangat berharga.
anthonyryan1
5
@osirisgothra Banyak penyedia hosting belum mendukung python3, jadi sebagian dari kita terpaksa menggunakan python2 untuk pengembangan back end. Meskipun itu seharusnya tidak ada hubungannya dengan mendapatkan ukuran terminal ...
whitebeard
4
@osirisgothra Karena ada banyak kode Python 2 yang akan membutuhkan terlalu banyak pekerjaan untuk port. Juga, Pypy masih memiliki dukungan Python 3 yang agak buruk.
Antimony
2
@whitebeard Apakah ada alasan pelanggan tidak dapat menginstal Python 3 pada VPS? Atau di 2016, apakah orang masih menggunakan shared hosting yang administratornya tidak mau menginstal Python 3? Misalnya, hosting bersama WebFaction telah python3.5diinstal.
Damian Yerrick
2
+1 untuk solusi yang juga berfungsi ketika input standar telah dialihkan dari file! Dengan solusi lain, saya mendapatkan Inappropriate ioctl for deviceerror / peringatan, atau mendapatkan nilai fallback yang didefinisikan sebesar 80.
pawamoy
65

menggunakan

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

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

EDIT : oh, maafkan saya. Itu bukan standar python lib, inilah sumber console.py (saya tidak tahu dari mana asalnya).

Modul ini tampaknya bekerja seperti itu: Ini memeriksa jika termcaptersedia, ketika ya. Itu menggunakannya; jika tidak memeriksa apakah terminal mendukung ioctlpanggilan khusus dan itu tidak berfungsi juga, ia memeriksa variabel lingkungan yang diekspor oleh beberapa shell. Ini mungkin hanya akan bekerja pada 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
sumber
5
Terima kasih atas balasan cepat, tetapi di sini ( effbot.org/zone/console-handbook.htm ) dikatakan bahwa "Modul Konsol saat ini hanya tersedia untuk Windows 95, 98, NT, dan 2000." Saya mencari solusi yang berfungsi di Linux. Mungkin tidak jelas dari tag, saya akan mengedit pertanyaan sesuai.
Sergey Golovchenko
2
karena modul "konsol" yang Anda gunakan ini tidak ada di pustaka python standar, Anda harus memberikan kode sumbernya atau setidaknya tautan ke sana.
nosklo
Aku turut berduka atas hal itu. Sebenarnya, saya tidak tahu modul itu. Saya mencoba mengimpor konsol dan berfungsi, saya menggunakan konsol. <tab> <tab> dan getTerminalSize () muncul. Alih-alih mencari di mana itu dari saya sudah diposting jawaban karena aku begitu beruntung dari kesederhanaan g
Johannes Weiss
Saya mungkin melihat modul "konsol" yang berbeda, dapatkah Anda memberikan tautan untuk yang Anda miliki?
Sergey Golovchenko
4
Oh, dan tidak menumpuk kode, tetapi "cr" adalah nama yang membingungkan karena itu menyiratkan tuple adalah (cols, rows). Pada kenyataannya, ini adalah kebalikannya.
Paul Du Bois
57

Kode di atas tidak mengembalikan hasil yang benar di linux saya karena winsize-struct memiliki 4 celana pendek yang tidak ditandatangani, bukan 2 celana pendek yang ditandatangani:

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 dan hp harus mengandung lebar dan tinggi piksel, tetapi tidak.

pascal
sumber
4
Inilah yang harus dilakukan; perhatikan bahwa jika Anda berniat untuk mencetak ke terminal, Anda harus menggunakan '1' sebagai deskriptor file (argumen pertama ioctl), karena stdin mungkin berupa pipa atau tty yang berbeda.
mic_e
1
Mungkin 0 harus diganti denganfcntl.ioctl(sys.stdin.fileno(), ...
raylu
4
ini adalah jawaban terbaik - pengguna Anda akan senang bahwa tidak ada subproses kejutan terjadi hanya untuk mendapatkan lebar istilah
zzzeek
4
ini memang jawaban terbersih. Saya pikir Anda harus menggunakan stdoutatau stderrbukannya stdin. stdinmungkin pipa. Anda mungkin juga ingin menambahkan baris seperti if not os.isatty(0): return float("inf").
mic_e
ini entah bagaimana berfungsi pada terminal chromebook yang tidak memiliki fungsi. +1
HyperNeutrino
39

Saya mencari-cari dan menemukan solusi untuk windows di:

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

dan solusi untuk linux di sini.

Jadi di sini adalah versi yang berfungsi baik di linux, os x dan 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
sumber
Anda menyelamatkan saya saat melakukan ini sendiri. Bekerja di Linux. Harus bekerja di Windows juga. Terima kasih!
Steve V.
30

Itu baik:

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

The shutilFungsi hanya bungkus sekitar ossatu yang menangkap beberapa kesalahan dan mengatur fallback, namun memiliki satu peringatan besar - rusak ketika pipa! , yang merupakan kesepakatan yang cukup besar.
Untuk mendapatkan ukuran terminal saat menggunakan pipa os.get_terminal_size(0)sebagai gantinya.

Argumen pertama 0adalah argumen yang menunjukkan bahwa deskriptor file stdin harus digunakan daripada stdout default. Kami ingin menggunakan stdin karena stdout melepaskan sendiri ketika sedang dipipihkan yang dalam hal ini menimbulkan kesalahan.

Saya sudah mencoba mencari tahu kapan masuk akal untuk menggunakan stdout daripada argumen stdin dan tidak tahu mengapa itu default di sini.

Granitosaurus
sumber
3
os.get_terminal_size()diperkenalkan dalam Python 3.3
villapx
Saya mencoba shutil pada awalnya, dan itu berhasil pada percobaan pertama. Saya menggunakan Python 3.6+.
Dan
Menggunakan os.get_terminal_size(0)akan macet jika Anda pipa ke stdin. Coba:echo x | python3 -c 'import os; print(os.get_terminal_size(0))'
ehabkost
19

Mulai dari Python 3.3 dengan langsung: 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
sumber
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
Ini adalah salinan dekat dari jawaban setahun yang lebih tua yang diberikan di atas.
Gringo Suave
6

Sepertinya ada beberapa masalah dengan kode itu, Johannes:

  • getTerminalSize perlu import os
  • apa env? Sepertinya os.environ.

Juga, mengapa beralih linesdan colssebelum kembali? Jika TIOCGWINSZdan sttykeduanya mengatakan linesitu cols, saya katakan biarkan seperti itu. Ini membingungkan saya selama 10 menit sebelum saya melihat ketidakkonsistenan.

Sridhar, saya tidak mendapatkan kesalahan itu ketika saya menyalurkan output. Saya cukup yakin itu tertangkap dengan baik di coba-kecuali.

pascal, "HHHH"tidak berfungsi pada mesin saya, tetapi "hh"tidak. Saya kesulitan menemukan dokumentasi untuk fungsi itu. Sepertinya itu tergantung platform.

chochem, berbadan hukum.

Ini versi saya:

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
sumber
Saya juga berkeliaran tentang hal envitu, dan memang env = os.environ, dari jawaban yang diterima .
sdaau
6

Banyak implementasi Python 2 di sini akan gagal jika tidak ada terminal pengendali saat Anda memanggil skrip ini. Anda dapat memeriksa sys.stdout.isatty () untuk menentukan apakah ini sebenarnya terminal, tetapi itu akan mengecualikan banyak kasus, jadi saya percaya cara paling pythonic untuk mengetahui ukuran terminal adalah dengan menggunakan paket kutukan bawaan.

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

Saya sedang mencoba solusi dari sini yang memanggil ke stty size:

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

Namun ini gagal bagi saya karena saya sedang mengerjakan skrip yang mengharapkan input diarahkan pada stdin, dan sttyakan mengeluh bahwa "stdin bukan terminal" dalam kasus itu.

Saya dapat membuatnya bekerja seperti ini:

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

Coba "berkah"

Saya mencari hal yang sama. Sangat mudah digunakan dan menawarkan alat untuk mewarnai, menata dan memposisikan di terminal. Yang Anda butuhkan semudah:

from blessings import Terminal

t = Terminal()

w = t.width
h = t.height

Bekerja seperti pesona di Linux. (Saya tidak yakin tentang MacOSX dan Windows)

Unduh dan dokumentasi di sini

atau Anda dapat menginstalnya dengan pip:

pip install blessings
Iman Akbari
sumber
Saat ini saya akan mengatakan mencoba "diberkati" yang merupakan garpu (dan peningkatan) "berkat" yang saat ini dikelola.
zezollo
1

@ reannual's jawaban berfungsi dengan baik, tetapi ada masalah dengan itu: os.popen sekarang sudah usang . The subprocessModul harus digunakan sebagai gantinya, jadi di sini adalah versi kode @ reannual yang menggunakan subprocessdan langsung menjawab pertanyaan (dengan memberikan lebar kolom secara langsung sebagai int:

import subprocess

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

Diuji pada OS X 10.9

becak
sumber
1

Jika Anda menggunakan Python 3.3 atau lebih tinggi, saya akan merekomendasikan built-in get_terminal_size()seperti yang telah direkomendasikan. Namun jika Anda terjebak dengan versi yang lebih lama dan menginginkan cara yang sederhana dan lintas platform untuk melakukan ini, Anda dapat menggunakan asciimatics . Paket ini mendukung versi Python kembali ke 2.7 dan menggunakan opsi serupa dengan yang disarankan di atas untuk mendapatkan ukuran terminal / konsol saat ini.

Cukup buat Screenkelas Anda dan gunakan dimensionsproperti untuk mendapatkan tinggi dan lebar. Ini telah terbukti berfungsi di Linux, OSX dan Windows.

Oh - dan pengungkapan penuh di sini: Saya adalah penulis, jadi silakan membuka masalah baru jika Anda memiliki masalah dalam menjalankannya.

Peter Brittain
sumber
0

Ini adalah versi yang seharusnya kompatibel dengan Linux dan Solaris. Berdasarkan posting dan komitmen dari madchine . Membutuhkan modul subproses.

def termsize ():
    impor shlex, subproses, ulang
    output = subprocess.check_output (shlex.split ('/ bin / stty -a'))
    m = penelitian ulang ('rows \ D + (? P \ d +); kolom \ D + (? P \ d +);', output)
    jika m:
        return m.group ('rows'), m.group ('kolom')
    meningkatkan OSError ('Respons buruk:% s'% (output))
>>> termsize ()
('40', '100')
Derrick Petzold
sumber