วิธีรับความกว้างหน้าต่างคอนโซล Linux ใน Python


264

มีวิธีใดในไพ ธ อนที่จะกำหนดความกว้างของคอนโซลโดยทางโปรแกรมหรือไม่ ฉันหมายถึงจำนวนตัวอักษรที่พอดีในหนึ่งบรรทัดโดยไม่มีการตัดคำไม่ใช่ความกว้างของพิกเซลของหน้าต่าง

แก้ไข

กำลังมองหาโซลูชันที่ใช้งานได้กับ Linux


ดูคำตอบนี้เพื่อหาคำตอบที่ครอบคลุมยิ่งขึ้นเพื่อให้มีกลไกการพิมพ์แบบ stackoverflow.com/questions/44129613/…
Riccardo Petraglia

คำตอบ:


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

ใช้คำสั่ง 'stty size' ซึ่งเป็นไปตามเธรดในรายการส่งเมลของ Python ซึ่งเป็นสากลที่สมเหตุสมผลบน linux มันเปิดคำสั่ง 'ขนาด stty' เป็นไฟล์ 'อ่าน' จากมันและใช้การแบ่งสตริงแบบง่ายเพื่อแยกพิกัด

ต่างจากค่า os.environ ["COLUMNS"] (ซึ่งฉันไม่สามารถเข้าถึงได้แม้จะใช้ bash เป็นเชลล์มาตรฐานของฉัน) ข้อมูลจะเป็นข้อมูลที่ทันสมัยในขณะที่ฉันเชื่อว่า os.environ ["COLUMNS"] ค่าจะใช้ได้เฉพาะในช่วงเวลาของการเปิดตัว python interpreter (สมมติว่าผู้ใช้ปรับขนาดหน้าต่างตั้งแต่นั้นมา)

(ดูคำตอบโดย @GringoSuave เกี่ยวกับวิธีการทำเช่นนี้กับ python 3.3+)


1
คุณสามารถทำให้สิ่งนี้ทำงานบน Solaris ได้เช่นกันหากแทนที่จะเป็น "ขนาด" คุณจะผ่าน "-a" จะมี "rows = Y; Column = X" ในเอาต์พุตที่คั่นด้วยเครื่องหมายอัฒภาค
โจเซฟการ์วิน

5
COLUMNS ไม่ได้ถูกส่งออกโดยค่าเริ่มต้นใน Bash นั่นคือสาเหตุที่ระบบปฏิบัติการ ["COLUMNS"] ไม่ทำงาน
Kjell Andreassen

38
rows, columns = subprocess.check_output(['stty', 'size']).split()สั้นกว่าเล็กน้อยบวกกับกระบวนการย่อยคืออนาคต
cdosborn

7
tput นั้นดีกว่า stty เนื่องจาก stty ไม่สามารถทำงานกับ PIPE ได้
liuyang1

5
rows, columns = subprocess.check_output(['stty', 'size']).decode().split()ถ้าคุณต้องการสตริง unicode สำหรับความเข้ากันได้ของ py2 / 3
Bryce Guinta

264

ไม่แน่ใจว่าทำไมมันอยู่ในโมดูลshutilแต่มันมาถึงที่นั่นใน Python 3.3 การสอบถามขนาดของเทอร์มินัลเอาท์พุท :

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

การใช้งานในระดับต่ำอยู่ในโมดูลระบบปฏิบัติการ ยังใช้งานได้ใน Windows

ขณะนี้มี backport สำหรับ Python 3.2 และด้านล่าง:


25
นั่นเป็นเพราะคุณไม่ควรใช้ 2.7 อีกต่อไปให้ข้ามไปที่ 3.x มันคุ้มค่า
anthonyryan1 1

5
@osirisgothra ผู้ให้บริการโฮสติ้งหลายรายยังไม่รองรับ python3 ดังนั้นเราบางคนถูกบังคับให้ใช้ python2 เพื่อการพัฒนาแบ็คเอนด์ แม้ว่ามันจะไม่เกี่ยวข้องกับการเพิ่มขนาดเทอร์มินัล ...
หนวดขาว

4
@osirisgothra เนื่องจากมีรหัส Python 2 จำนวนมากที่จะทำงานพอร์ตมากเกินไป นอกจากนี้ Pypy ยังรองรับ Python 3 ได้ไม่ดีนัก
พลวง

2
@whitebeard มีเหตุผลที่สมาชิกไม่สามารถติดตั้ง Python 3 บน VPS ได้หรือไม่? หรือในปี 2559 คนยังใช้โฮสติ้งที่ใช้ร่วมกันซึ่งผู้ดูแลระบบไม่ต้องการติดตั้ง Python 3 หรือไม่ ตัวอย่างเช่นโฮสติ้งที่ใช้ร่วมกันของ WebFaction ได้python3.5ติดตั้งแล้ว
Damian Yerrick

2
+1 สำหรับโซลูชันที่ใช้งานได้เมื่อเปลี่ยนเส้นทางอินพุตมาตรฐานจากไฟล์! ด้วยโซลูชันอื่น ๆ ฉันได้รับInappropriate ioctl for deviceข้อผิดพลาด / คำเตือนหรือรับค่าทางเลือกที่กำหนดไว้ที่ 80
pawamoy

65

ใช้

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

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

แก้ไข : โอ้ฉันขอโทษ นี่ไม่ใช่ lib มาตรฐานของไพ ธ อนนี่คือที่มาของ console.py (ฉันไม่รู้ว่ามาจากไหน)

โมดูลดูเหมือนจะทำงานดังนี้: ตรวจสอบว่าtermcapมีให้หรือไม่เมื่อใช่ มันใช้สิ่งนั้น ถ้าไม่มีมันจะตรวจสอบว่าเทอร์มินัลรองรับการioctlโทรพิเศษและไม่สามารถใช้งานได้เช่นกันมันจะตรวจสอบตัวแปรสภาพแวดล้อมที่บางเชลล์ส่งออกไป สิ่งนี้อาจใช้ได้กับ 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
ขอบคุณสำหรับการตอบกลับอย่างรวดเร็ว แต่ที่นี่ ( effbot.org/zone/console-handbook.htm ) บอกว่า "โมดูลคอนโซลปัจจุบันมีเฉพาะสำหรับ Windows 95, 98, NT และ 2000" ฉันกำลังมองหาโซลูชันที่ใช้งานได้กับ Linux อาจไม่ชัดเจนจากแท็กฉันจะแก้ไขคำถามตาม
Sergey Golovchenko

2
เนื่องจากโมดูล "คอนโซล" นี้ที่คุณใช้ไม่ได้อยู่ในไลบรารีหลามมาตรฐานคุณควรระบุซอร์สโค้ดของมันหรืออย่างน้อยก็ลิงค์ไปยังมัน
nosklo

ฉันขอโทษด้วย ที่จริงฉันไม่รู้โมดูลนั้น ฉันพยายามนำเข้าคอนโซลแล้วก็ใช้งานได้ฉันใช้คอนโซล <tab> <tab> และ getTerminalSize () ปรากฏขึ้น แทนที่จะมองที่มันจากฉันแล้วโพสต์คำตอบเพราะผมเป็นโชคดีของความเรียบง่ายกรัม
ฮันเนสไวส์

ฉันอาจกำลังดูโมดูล "คอนโซล" อื่นคุณช่วยระบุลิงก์สำหรับโมดูลที่คุณมีหรือไม่
Sergey Golovchenko

4
โอ้และไม่ควรพะวงกับรหัส แต่ "cr" เป็นชื่อที่สับสนเพราะมันบอกเป็นนัย ๆ ว่า tuple คือ (cols, แถว) ในความเป็นจริงมันเป็นสิ่งที่ตรงกันข้าม
Paul Du Bois

57

โค้ดด้านบนไม่ส่งคืนผลลัพธ์ที่ถูกต้องบน linux ของฉันเพราะ winsize-struct มีกางเกงขาสั้น 4 ตัวที่ไม่ได้ลงนามไม่ใช่กางเกงขาสั้น 2 ลายเซ็น:

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 และ hp ควรมีความกว้างและความสูงของพิกเซล แต่ไม่ใช่


4
นี่คือวิธีที่ควรทำ โปรดทราบว่าหากคุณตั้งใจจะพิมพ์ไปที่เครื่องเทอร์มินัลคุณควรใช้ '1' เป็นไฟล์ descriptor (อาร์กิวเมนต์แรกของ ioctl) เนื่องจาก stdin อาจเป็นไพพ์หรือ tty อื่น
mic_e

1
บางที 0 ควรถูกแทนที่ด้วยfcntl.ioctl(sys.stdin.fileno(), ...
raylu

4
นี่คือคำตอบที่ดีที่สุด - ผู้ใช้ของคุณจะมีความสุขที่ไม่มีกระบวนการย่อยที่น่าประหลาดใจเกิดขึ้นเพียงเพื่อให้ได้ความกว้างของคำ
zzzeek

4
นี่เป็นคำตอบที่สะอาดที่สุด ฉันคิดว่าคุณควรใช้stdoutหรือstderrแทนstdinแม้ว่า stdinอาจเป็นท่อได้เป็นอย่างดี if not os.isatty(0): return float("inf")นอกจากนี้คุณยังอาจต้องการที่จะเพิ่มบรรทัดเช่น
mic_e

วิธีนี้ใช้ได้กับเทอร์มินัล chromebook ที่ไม่มีประโยชน์อะไรเลย +1
HyperNeutrino

39

ฉันค้นหารอบ ๆ และพบวิธีแก้ปัญหาสำหรับ windows ที่:

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

และทางออกสำหรับ linux ที่นี่

ดังนั้นนี่คือเวอร์ชันที่ใช้งานได้ทั้งบน linux, os x และ 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

คุณช่วยฉันเวลาที่ทำสิ่งนี้ด้วยตัวเอง ทำงานบน Linux ควรทำงานบน Windows เช่นกัน ขอบคุณ!
Steve V.

30

มันทั้ง:

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

shutilฟังก์ชั่นเป็นเพียงเสื้อคลุมรอบosหนึ่งที่จับข้อผิดพลาดบางและการตั้งค่าสำรอง แต่ก็มีหนึ่งข้อแม้มาก - จะแบ่งเมื่อท่อ! ซึ่งเป็นเรื่องใหญ่มาก
เพื่อรับขนาดเทอร์มินัลเมื่อใช้ท่อos.get_terminal_size(0)แทน

อาร์กิวเมนต์แรก0คืออาร์กิวเมนต์ที่ระบุว่าควรใช้ stdin file descriptor แทนค่าดีฟอลต์ stdout เราต้องการใช้ stdin เพราะ stdout แยกตัวเองเมื่อมันถูก piped ซึ่งในกรณีนี้ทำให้เกิดข้อผิดพลาด

ฉันพยายามคิดออกว่าจะใช้ stdout แทนที่จะเป็นอาร์กิวเมนต์ stdin เมื่อไรและไม่รู้ว่าทำไมจึงเป็นค่าเริ่มต้นที่นี่


3
os.get_terminal_size()เปิดตัวใน Python 3.3
villapx

ฉันลองปิดเครื่องในตอนแรกและก็ลองครั้งแรก ฉันใช้ Python 3.6+
Dan

การใช้os.get_terminal_size(0)จะมีปัญหาหากคุณไปที่ stdin ลอง:echo x | python3 -c 'import os; print(os.get_terminal_size(0))'
ehabkost

19

เริ่มต้นที่ Python 3.3 ตรงไปตรงมา: 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

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
นี่เป็นสำเนาใกล้ของคำตอบที่มีอายุมากกว่าหนึ่งปีที่ระบุไว้ด้านบน
Gringo Suave

6

ดูเหมือนว่ามีปัญหาบางอย่างกับรหัสนั้น Johannes:

  • getTerminalSize ต้องการ import os
  • คือenvอะไร os.environดูเหมือนว่า

นอกจากนี้ทำไมสลับlinesและcolsก่อนกลับมา ถ้าTIOCGWINSZและsttyทั้งคู่พูดเช่นlinesนั้นcolsฉันก็บอกว่าปล่อยไว้อย่างนั้น เรื่องนี้ทำให้ฉันสับสน 10 นาทีก่อนที่ฉันจะสังเกตเห็นความไม่ลงรอยกัน

Sridhar ฉันไม่ได้รับข้อผิดพลาดนั้นเมื่อฉันส่งออก pip ฉันค่อนข้างมั่นใจว่ามันถูกจับได้อย่างถูกต้องในการลองยกเว้น

ปาสกาล"HHHH"ไม่ทำงานบนเครื่องของฉัน แต่"hh"ทำ ฉันมีปัญหาในการค้นหาเอกสารสำหรับฟังก์ชั่นนั้น ดูเหมือนว่ามันขึ้นอยู่กับแพลตฟอร์ม

chochem จัดตั้งขึ้น

นี่คือรุ่นของฉัน:

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)

ฉันหลงเกี่ยวกับenvเกินไปและเป็นจริงenv = os.environจากคำตอบที่ได้รับการยอมรับ
sdaau

6

การใช้งาน Python 2 จำนวนมากที่นี่จะล้มเหลวหากไม่มีเทอร์มินัลการควบคุมเมื่อคุณเรียกใช้สคริปต์นี้ คุณสามารถตรวจสอบ sys.stdout.isatty () เพื่อตรวจสอบว่านี่เป็นเทอร์มินัลหรือไม่ แต่จะแยกเคสออกเป็นจำนวนมากดังนั้นฉันเชื่อว่าวิธีที่ไพเราะที่สุดในการหาขนาดเทอร์มินัลคือการใช้แพคเกจ

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

2

ฉันกำลังลองวิธีแก้ปัญหาจากที่นี่ซึ่งเรียกไปที่stty size:

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

อย่างไรก็ตามสิ่งนี้ล้มเหลวสำหรับฉันเพราะฉันทำงานกับสคริปต์ที่คาดว่าจะมีการเปลี่ยนเส้นทางอินพุตใน stdin และsttyจะบ่นว่า "stdin ไม่ใช่เทอร์มินัล" ในกรณีนั้น

ฉันสามารถทำให้มันทำงานเช่นนี้:

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

2

ลอง"พร"

ฉันกำลังมองหาสิ่งเดียวกัน มันใช้งานง่ายมากและมีเครื่องมือสำหรับการระบายสีจัดแต่งทรงผมและวางตำแหน่งในเครื่อง สิ่งที่คุณต้องการนั้นง่ายพอ ๆ กับ:

from blessings import Terminal

t = Terminal()

w = t.width
h = t.height

ทำงานได้อย่างมีเสน่ห์ใน Linux (ฉันไม่แน่ใจเกี่ยวกับ MacOSX และ Windows)

ดาวน์โหลดและเอกสารประกอบที่นี่

หรือคุณสามารถติดตั้งได้ด้วย pip:

pip install blessings

ทุกวันนี้ฉันจะบอกว่าลอง "มีความสุข" ซึ่งเป็นทางแยกที่ได้รับการบำรุงรักษาในปัจจุบัน
zezollo

1

@ คำตอบ reannual ทำงานได้ดี แต่มีปัญหากับมันจะเลิกตอนนี้os.popen subprocessโมดูลควรจะนำมาใช้แทนเพื่อให้ที่นี่เป็นรุ่นของรหัส @ reannual ที่ใช้subprocessโดยตรงและตอบคำถาม (โดยการให้ความกว้างของคอลัมน์โดยตรงเป็นint:

import subprocess

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

ทดสอบบน OS X 10.9


1

หากคุณใช้ Python 3.3 ขึ้นไปฉันขอแนะนำ built-in get_terminal_size()ตามที่แนะนำแล้ว แต่ถ้าคุณจะติดอยู่กับรุ่นเก่าและต้องการวิธีง่ายๆที่ข้ามแพลตฟอร์มการทำเช่นนี้คุณสามารถใช้asciimatics แพ็คเกจนี้รองรับเวอร์ชั่นของ Python กลับไปที่ 2.7 และใช้ตัวเลือกที่คล้ายกับที่แนะนำไว้ข้างต้นเพื่อรับขนาดเทอร์มินัล / คอนโซลปัจจุบัน

เพียงสร้างScreenชั้นเรียนของคุณและใช้dimensionsคุณสมบัตินี้เพื่อให้ได้ความสูงและความกว้าง สิ่งนี้ได้รับการพิสูจน์แล้วว่าทำงานบน Linux, OSX และ Windows

โอ้ - และการเปิดเผยอย่างสมบูรณ์ที่นี่: ฉันเป็นผู้เขียนดังนั้นโปรดเปิดประเด็นใหม่หากคุณมีปัญหาในการทำให้เรื่องนี้ทำงานได้


0

นี่คือรุ่นที่ควรใช้กับ Linux และ Solaris ขึ้นอยู่กับการโพสต์และ commments จากmadchine ต้องการโมดูลย่อย

def termize ():
    นำเข้า shlex กระบวนการย่อยอีกครั้ง
    output = subprocess.check_output (shlex.split ('/ bin / stty -a'))
    m = re.search ('rows \ D + (? P \ d +); คอลัมน์ \ D + (? P \ d +);', เอาต์พุต)
    ถ้า m:
        ส่งคืน m.group ('แถว'), m.group ('คอลัมน์')
    เพิ่ม OSError ('การตอบสนองที่ไม่ดี:% s'% (เอาต์พุต))
>>> เงื่อนไข ()
('40', '100')
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.