如何在Python中检测控制台是否支持ANSI转义码?

发布于 2024-12-05 11:19:28 字数 741 浏览 2 评论 0原文

为了检测控制台是否正确 sys.stderrsys.stdout,我做了以下测试:

if hasattr(sys.stderr, "isatty") and sys.stderr.isatty():
   if platform.system()=='Windows':
       # win code (ANSI not supported but there are alternatives)
   else:
       # use ANSI escapes
else:
   # no colors, usually this is when you redirect the output to a file

现在,通过运行此 Python 代码时,问题变得更加复杂IDE(如 PyCharm)。最近 PyCharm 添加了对 ANSI 的支持,但第一次测试失败:它具有 isatty 属性,但设置为 False

我想修改逻辑,以便它能够正确检测输出是否支持 ANSI 着色。一个要求是,在任何情况下,当输出重定向到文件时,我都不应该输出任何内容(对于控制台来说,这是可以接受的)。

更新

https://gist.github.com/1316877 添加了更复杂的 ANSI 测试脚本

In order to detect if console, correctly sys.stderr or sys.stdout, I was doing the following test:

if hasattr(sys.stderr, "isatty") and sys.stderr.isatty():
   if platform.system()=='Windows':
       # win code (ANSI not supported but there are alternatives)
   else:
       # use ANSI escapes
else:
   # no colors, usually this is when you redirect the output to a file

Now the problem became more complex while running this Python code via an IDE (like PyCharm). Recently PyCharm added support for ANSI, but the first test fails: it has the isatty attribute but it is set to False.

I want to modify the logic so it will properly detect if the output supports ANSI coloring. One requirement is that under no circumstance I should output something out when the output is redirected to a file (for console it would be acceptable).

Update

Added more complex ANSI test script at https://gist.github.com/1316877

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

很糊涂小朋友 2024-12-12 11:19:28

Django 用户可以使用 django.core.management.color.supports_color 函数。

if supports_color():
    ...

他们使用的代码是:

def supports_color():
    """
    Returns True if the running system's terminal supports color, and False
    otherwise.
    """
    plat = sys.platform
    supported_platform = plat != 'Pocket PC' and (plat != 'win32' or
                                                  'ANSICON' in os.environ)
    # isatty is not always implemented, #6223.
    is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
    return supported_platform and is_a_tty

请参阅 https://github。 com/django/django/blob/master/django/core/management/color.py

Django users can use django.core.management.color.supports_color function.

if supports_color():
    ...

The code they use is:

def supports_color():
    """
    Returns True if the running system's terminal supports color, and False
    otherwise.
    """
    plat = sys.platform
    supported_platform = plat != 'Pocket PC' and (plat != 'win32' or
                                                  'ANSICON' in os.environ)
    # isatty is not always implemented, #6223.
    is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
    return supported_platform and is_a_tty

See https://github.com/django/django/blob/master/django/core/management/color.py

兔姬 2024-12-12 11:19:28

我可以告诉你其他人是如何解决这个问题的,但这并不漂亮。如果您以 ncurses 为例(它需要能够在各种不同的终端上运行),您会发现它们使用 终端能力数据库,用于存储各种终端及其能力。关键是,即使他们也永远无法自动“检测”这些东西。

我不知道是否有跨平台 termcap,但可能值得您花时间寻找它。即使它在那里,它也可能没有列出您的终端,您可能必须手动添加它。

I can tell you how others have solved this problem, but it's not pretty. If you look at ncurses as an example (which needs to be able to run on all kinds of different terminals), you'll see that they use a terminal capabilities database to store every kind of terminal and its capabilities. The point being, even they were never able to automatically "detect" these things.

I don't know if there's a cross-platform termcap, but it's probably worth your time to look for it. Even if it's out there though, it may not have your terminal listed and you may have to manually add it.

我纯我任性 2024-12-12 11:19:28

\x1B[6n 是一个标准的(据我所知)ANSI 转义码,用于查询用户光标的位置。如果发送到标准输出,终端应将 \x1B[{line};{column}R 写入标准输入。如果达到此结果,则可以假设支持 ANSI 转义码。主要问题是检测这个回复。

Windows

msvcrt.getch 可用于从 stdin 检索字符,而无需等待按下 Enter 键。这与 msvcrt.kbhit 结合使用,后者检测 stdin 是否正在等待读取,并生成在本文的带有注释的代码部分中找到的代码。

Unix/with termios

警告:我(不明智地)没有测试这个特定的 tty/select/termios 代码,但过去知道类似的代码可以工作。
可以使用 tty.setrawselect.select 复制 getchkbhit。因此,我们可以如下定义这些函数:

from termios import TCSADRAIN, tcgetattr, tcsetattr
from select import select
from tty import setraw
from sys import stdin

def getch() -> bytes:
    fd = stdin.fileno()                        # get file descriptor of stdin
    old_settings = tcgetattr(fd)               # save settings (important!)

    try:                                       # setraw accomplishes a few things,
        setraw(fd)                             # such as disabling echo and wait.

        return stdin.read(1).encode()          # consistency with the Windows func
    finally:                                   # means output should be in bytes
        tcsetattr(fd, TCSADRAIN, old_settings) # finally, undo setraw (important!)

def kbhit() -> bool:                           # select.select checks if fds are
    return bool(select([stdin], [], [], 0)[0]) # ready for reading/writing/error

然后可以将其与以下代码一起使用。

带注释的代码

from sys import stdin, stdout

def isansitty() -> bool:
    """
    The response to \x1B[6n should be \x1B[{line};{column}R according to
    https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797. If this
    doesn't work, then it is unlikely ANSI escape codes are supported.
    """

    while kbhit():                         # clear stdin before sending escape in
        getch()                            # case user accidentally presses a key

    stdout.write("\x1B[6n")                # alt: print(end="\x1b[6n", flush=True)
    stdout.flush()                         # double-buffered stdout needs flush 

    stdin.flush()                          # flush stdin to make sure escape works
    if kbhit():                            # ANSI won't work if stdin is empty
        if ord(getch()) == 27 and kbhit(): # check that response starts with \x1B[
            if getch() == b"[":
                while kbhit():             # read stdin again, to remove the actual
                    getch()                # value returned by the escape

                return stdout.isatty()     # lastly, if stdout is a tty, ANSI works
                                           # so True should be returned. Otherwise,
    return False                           # return False

带注释的完整代码

如果您需要,这里是原始代码。

from sys import stdin, stdout
from platform import system


if system() == "Windows":
    from msvcrt import getch, kbhit

else:
    from termios import TCSADRAIN, tcgetattr, tcsetattr
    from select import select
    from tty import setraw
    from sys import stdin

    def getch() -> bytes:
        fd = stdin.fileno()
        old_settings = tcgetattr(fd)

        try:
            setraw(fd)

            return stdin.read(1).encode()
        finally:
            tcsetattr(fd, TCSADRAIN, old_settings)

    def kbhit() -> bool:
        return bool(select([stdin], [], [], 0)[0])

def isansitty() -> bool:
    """
    Checks if stdout supports ANSI escape codes and is a tty.
    """

    while kbhit():
        getch()

    stdout.write("\x1b[6n")
    stdout.flush()

    stdin.flush()
    if kbhit():
        if ord(getch()) == 27 and kbhit():
            if getch() == b"[":
                while kbhit():
                    getch()

                return stdout.isatty()

    return False

来源

不分先后:

\x1B[6n is a standard (as far as I am aware) ANSI escape code to query the position of the user's cursor. If sent to stdout, the terminal should write \x1B[{line};{column}R to stdin. It can be assumed that ANSI escape codes are supported if this result is achieved. The main problem becomes detecting this reply.

Windows

msvcrt.getch can be used to retrieve a char from stdin, without waiting for enter to be pressed. This in combination with msvcrt.kbhit, which detects if stdin is waiting to be read yields code found in the Code w/ Comments section of this post.

Unix/with termios

Warning: I have (inadvisably) not tested this specific tty/select/termios code, but have known similiar code to work in the past.
getch and kbhit can be replicated using tty.setraw and select.select. Thus we can define these functions as follows:

from termios import TCSADRAIN, tcgetattr, tcsetattr
from select import select
from tty import setraw
from sys import stdin

def getch() -> bytes:
    fd = stdin.fileno()                        # get file descriptor of stdin
    old_settings = tcgetattr(fd)               # save settings (important!)

    try:                                       # setraw accomplishes a few things,
        setraw(fd)                             # such as disabling echo and wait.

        return stdin.read(1).encode()          # consistency with the Windows func
    finally:                                   # means output should be in bytes
        tcsetattr(fd, TCSADRAIN, old_settings) # finally, undo setraw (important!)

def kbhit() -> bool:                           # select.select checks if fds are
    return bool(select([stdin], [], [], 0)[0]) # ready for reading/writing/error

This can then be used with the below code.

Code w/ Comments

from sys import stdin, stdout

def isansitty() -> bool:
    """
    The response to \x1B[6n should be \x1B[{line};{column}R according to
    https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797. If this
    doesn't work, then it is unlikely ANSI escape codes are supported.
    """

    while kbhit():                         # clear stdin before sending escape in
        getch()                            # case user accidentally presses a key

    stdout.write("\x1B[6n")                # alt: print(end="\x1b[6n", flush=True)
    stdout.flush()                         # double-buffered stdout needs flush 

    stdin.flush()                          # flush stdin to make sure escape works
    if kbhit():                            # ANSI won't work if stdin is empty
        if ord(getch()) == 27 and kbhit(): # check that response starts with \x1B[
            if getch() == b"[":
                while kbhit():             # read stdin again, to remove the actual
                    getch()                # value returned by the escape

                return stdout.isatty()     # lastly, if stdout is a tty, ANSI works
                                           # so True should be returned. Otherwise,
    return False                           # return False

Complete Code w/o Comments

In case you want it, here is the raw code.

from sys import stdin, stdout
from platform import system


if system() == "Windows":
    from msvcrt import getch, kbhit

else:
    from termios import TCSADRAIN, tcgetattr, tcsetattr
    from select import select
    from tty import setraw
    from sys import stdin

    def getch() -> bytes:
        fd = stdin.fileno()
        old_settings = tcgetattr(fd)

        try:
            setraw(fd)

            return stdin.read(1).encode()
        finally:
            tcsetattr(fd, TCSADRAIN, old_settings)

    def kbhit() -> bool:
        return bool(select([stdin], [], [], 0)[0])

def isansitty() -> bool:
    """
    Checks if stdout supports ANSI escape codes and is a tty.
    """

    while kbhit():
        getch()

    stdout.write("\x1b[6n")
    stdout.flush()

    stdin.flush()
    if kbhit():
        if ord(getch()) == 27 and kbhit():
            if getch() == b"[":
                while kbhit():
                    getch()

                return stdout.isatty()

    return False

Sources

In no particular order:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文