在 Python 3 中使用 ANSI 序列确定终端光标位置 [英] Determine the terminal cursor position with an ANSI sequence in Python 3

查看:25
本文介绍了在 Python 3 中使用 ANSI 序列确定终端光标位置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

终端

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

Python

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

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

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

#!/bin/bash# 脚本退出时恢复终端设置.termios="$(stty -g)"陷阱stty‘$termios’"退出# 禁用 ICANON ECHO.也应该禁用 CREAD.stty -icanon -echo# 请求光标坐标printf '\033[6n'# 从标准输入读取响应;注意,它以 R 结尾,而不是换行符读取 -d "R" 行列# 清理行列(来自 \033[rows;cols -- 最后的 R 被吃掉了)rowscols="${rowscols//[^0-9;]/}"rowscols=("${rowscols//;/}")printf '(行 %d,列 %d)' ${rowscols[0]} ${rowscols[1]}# 重置原始终端设置.stty$termios"

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

解决方案

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

curpos.py

import os, re, sys, termios, ttydef getpos():buf = ""标准输入 = sys.stdin.fileno()tattr = termios.tcgetattr(stdin)尝试:tty.setcbreak(stdin, termios.TCSANOW)sys.stdout.write("\x1b[6n")sys.stdout.flush()为真:buf += sys.stdin.read(1)如果 buf[-1] == "R":休息最后:termios.tcsetattr(stdin, termios.TCSANOW, tattr)# 读取实际值,但如果在读取时出现按键怎么办# 来自标准输入?作为肮脏的工作,如果失败, getpos() 返回:无尝试:匹配 = re.match(r"^\x1b\[(\d*);(\d*)R", buf)组=matches.groups()除了属性错误:返回无返回(整数(组[0]),整数(组[1]))如果 __name__ == "__main__":打印(getpos())

示例输出

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

警告

这并不完美.为了使它更健壮,一个例程在从 stdin 读取的同时对用户的击键进行分类会很好.

I want to write a little script which prints images to the terminal with /usr/lib/w3mimgdisplay(like in mac osx lsi). Therefore i need the the actual cursor position (or caret position) when the script has started. So far i figured out to get the cursor position in a shell with an ANSI sequence:

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

This is how the response of this ANSI sequence look like (urvxt and bash - don't know if that is important). So this sequence prints out the result (^[[2;1R) immediately. And this is what i don't understand. How is this done? If i write a very simple shell script, just with that instruction and strace the script, this doesn't clear up things ether. What the? Then i try to figure out how this happens by looking in the terminfo manpage. Can't find it here (maybe i didn't try hard enough). At this point i find myself very confused about this concept. Does the terminal write the position even to stdout?

Terminal

#!/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

First i tried to use subprocess.check_output which of course just returns the the string i've echoed. How do i capture the response to this ANSI sequence?

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

I also tried a lot of other things, like reading the stdin and stdout!? With and without threads, but all of that was more a guessing and mocking around rather than knowing what to do. I also search the Internet for quite a while, hoping to find an example of how to do this, but whit no luck. I've found this answer to the same question: https://stackoverflow.com/a/35526389/2787738 but this don't work. Actually i don't know if this has ever worked, because in this answer the ANSI sequence is written to stdout before it starts reading from stdin? Here i realised again that i don't understand the concept/mechanism how these ANSI sequences really work. So at this point every explanation which clears things a very much appreciated. The most helpful post i found was this one: https://www.linuxquestions.org/questions/programming-9/get-cursor-position-in-c-947833/. In this thread someone posted this bash script:

#!/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"

Here we can see that indeed the response is somehow magically appears one the screen :). This is why this script disables echoing on the terminal and after reading the response it resets the original terminal settings via stty.

解决方案

Here is a POC snippet, how to read the current cursor position via an ansi/vt100 controll sequence.

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())

example output

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

warning

This is not perfect. To make it more robust, a routine which sorts out keystrokes from the user while reading from stdin would be nice.

这篇关于在 Python 3 中使用 ANSI 序列确定终端光标位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆