Python 3 中用 ANSI 序列确定终端光标位置
Determine the terminal cursor position with an ANSI sequence in Python 3
我想写一个小脚本,用 /usr/lib/w3mimgdisplay
将图像打印到终端(如 mac osx lsi
)。因此,我需要脚本启动时的实际光标位置(或插入符号位置)。到目前为止,我想出了使用 ANSI 序列在 shell 中获取光标位置的方法:
$ echo -en "\e[6n"
^[[2;1R$ 1R
这就是这个 ANSI 序列的响应方式(urvxt 和 bash - 不知道这是否重要)。所以这个序列立即打印出结果(^[[2;1R
)。这就是我不明白的。这是怎么做到的?如果我编写一个非常简单的 shell 脚本,仅使用该指令并对脚本进行 strace,这并不能彻底解决问题。什么?然后我试图通过查看 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, "[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
,这当然只是 returns 我回显的字符串。我如何捕获对此 ANSI 序列的响应?
>>> subprocess.check_output(["echo", "-en", "\x1b[6n"])
b"\x1b[6n"
我还尝试了很多其他的东西,比如读取标准输入和标准输出!?有和没有线程,但所有这些更多的是猜测和嘲笑,而不是知道该怎么做。我也在 Internet 上搜索了很长时间,希望找到一个如何执行此操作的示例,但没有成功。我找到了同一个问题的答案: 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/。在此线程中,有人发布了此 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 '3[6n'
# Read response from standard input; note, it ends at R, not at newline
read -d "R" rowscols
# Clean up the rowscols (from 3[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)
警告
这并不完美。为了使其更健壮,在从标准输入读取数据的同时整理出用户击键的例程会很好。
我想写一个小脚本,用 /usr/lib/w3mimgdisplay
将图像打印到终端(如 mac osx lsi
)。因此,我需要脚本启动时的实际光标位置(或插入符号位置)。到目前为止,我想出了使用 ANSI 序列在 shell 中获取光标位置的方法:
$ echo -en "\e[6n"
^[[2;1R$ 1R
这就是这个 ANSI 序列的响应方式(urvxt 和 bash - 不知道这是否重要)。所以这个序列立即打印出结果(^[[2;1R
)。这就是我不明白的。这是怎么做到的?如果我编写一个非常简单的 shell 脚本,仅使用该指令并对脚本进行 strace,这并不能彻底解决问题。什么?然后我试图通过查看 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, "[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
,这当然只是 returns 我回显的字符串。我如何捕获对此 ANSI 序列的响应?
>>> subprocess.check_output(["echo", "-en", "\x1b[6n"])
b"\x1b[6n"
我还尝试了很多其他的东西,比如读取标准输入和标准输出!?有和没有线程,但所有这些更多的是猜测和嘲笑,而不是知道该怎么做。我也在 Internet 上搜索了很长时间,希望找到一个如何执行此操作的示例,但没有成功。我找到了同一个问题的答案:
#!/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 '3[6n'
# Read response from standard input; note, it ends at R, not at newline
read -d "R" rowscols
# Clean up the rowscols (from 3[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)
警告
这并不完美。为了使其更健壮,在从标准输入读取数据的同时整理出用户击键的例程会很好。