python - 在 Python 3 中使用 ANSI 序列确定终端光标位置

标签 python python-3.x terminal ansi-escape

我想编写一个小脚本,使用 /usr/lib/w3mimgdisplay 将图像打印到终端(就像在 mac osx lsi 中一样)。因此,当脚本启动时,我需要实际的光标位置(或插入符号位置)。到目前为止,我想出了如何使用 ANSI 序列获取 shell 中的光标位置:

$ echo -en "\e[6n"
^[[2;1R$ 1R

这就是这个 ANSI 序列的响应的样子(urvxt 和 bash - 不知道这是否重要)。所以这个序列立即打印出结果(^[[2;1R)。这就是我不明白的地方。这是怎么做到的?如果我编写一个非常简单的 shell 脚本,仅使用该指令并跟踪脚本,这并不能解决问题。什么?然后我尝试通过查看 terminfo 联机帮助页来弄清楚这是如何发生的。在这里找不到它(也许我没有努力)。此时我发现自己对这个概念非常困惑。终端是否将位置写入标准输出?

终端

#!/bin/bash
echo -en "\e[6n"

$ strace sh curpos.sh
[...]
read(255, "#!/bin/bash\necho -en \"\\e[6n\"\n", 29) = 29
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 8), ...}) = 0
write(1, "\33[6n", 4)                   = 4
^[[54;21Rread(255, "", 29)                       = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Python

首先我尝试使用subprocess.check_output,它当然只返回我回显的字符串。我如何捕获对此 ANSI 序列的响应?

>>> subprocess.check_output(["echo", "-en", "\x1b[6n"])
b"\x1b[6n"

我还尝试了很多其他的事情,比如读取标准输入和标准输出!?有或没有线程,但所有这一切更多的是猜测和 mock ,而不是知道该怎么做。我也在互联网上搜索了很长一段时间,希望找到一个如何做到这一点的例子,但没有运气。我找到了同一问题的答案:https://stackoverflow.com/a/35526389/2787738但这行不通。实际上我不知道这是否有效,因为在这个答案中,ANSI 序列在开始从标准输入读取之前被写入标准输出?在这里我再次意识到我不理解这些 ANSI 序列如何真正工作的概念/机制。因此,在这一点上,我们非常感谢每一个澄清事情的解释。我发现的最有帮助的帖子是这个:https://www.linuxquestions.org/questions/programming-9/get-cursor-position-in-c-947833/ 。在这个帖子中,有人发布了这个 bash 脚本:

#!/bin/bash
# Restore terminal settings when the script exits.
termios="$(stty -g)"
trap "stty '$termios'" EXIT
# Disable ICANON ECHO. Should probably also disable CREAD.
stty -icanon -echo
# Request cursor coordinates
printf '\033[6n'
# Read response from standard input; note, it ends at R, not at newline
read -d "R" rowscols
# Clean up the rowscols (from \033[rows;cols -- the R at end was eaten)
rowscols="${rowscols//[^0-9;]/}"
rowscols=("${rowscols//;/ }")
printf '(row %d, column %d) ' ${rowscols[0]} ${rowscols[1]}
# Reset original terminal settings.
stty "$termios"

在这里我们可以看到,响应确实以某种方式神奇地出现在屏幕上:)。这就是为什么此脚本禁用终端上的回显,并在读取响应后通过 stty 重置原始终端设置。

最佳答案

这是一个 POC 片段,说明如何通过 ansi/vt100 控制序列读取当前光标位置。

curpos.py

import os, re, sys, termios, tty

def getpos():

    buf = ""
    stdin = sys.stdin.fileno()
    tattr = termios.tcgetattr(stdin)

    try:
        tty.setcbreak(stdin, termios.TCSANOW)
        sys.stdout.write("\x1b[6n")
        sys.stdout.flush()

        while True:
            buf += sys.stdin.read(1)
            if buf[-1] == "R":
                break

    finally:
        termios.tcsetattr(stdin, termios.TCSANOW, tattr)

    # reading the actual values, but what if a keystroke appears while reading
    # from stdin? As dirty work around, getpos() returns if this fails: None
    try:
        matches = re.match(r"^\x1b\[(\d*);(\d*)R", buf)
        groups = matches.groups()
    except AttributeError:
        return None

    return (int(groups[0]), int(groups[1]))

if __name__ == "__main__":
    print(getpos())

示例输出

$ python ./curpos.py
(2, 1)

警告

这并不完美。为了使其更加健壮,在从标准输入读取数据的同时对用户的击键进行分类的例程会很好。

关于python - 在 Python 3 中使用 ANSI 序列确定终端光标位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46651602/

相关文章:

Python3 'Cannot import name ' cached_property'

python - Action 插件和 Ansible 模块之间有什么关系?

python - 守护进程 python 脚本后找不到守护进程

python-3.x - Pandas 错误: Passing lists with missing labels

python - 如果某行包含 CSV 文件中的字符串,则删除该行

linux - 查找音频文件中的每个单词

bash - 在 OS X 终端修改 Bash 提示前缀

带有重音符号的 Python 转储 json

Python 3,循环中的变量

c - 如何从 C 中的 Unix 终端获取输入文件名?