解决sys.stdin.read()与subprocess调用vim后的终端异常问题
管道编辑交互模式下终端异常的原因与修复
我要开发类似git rebase -i HEAD~6的管道编辑交互功能,参考moreutils的vipe工具。在原有示例代码基础上添加管道输入读取逻辑后,执行echo "some words" | python the_code.py并退出Vim(:q!),终端出现异常:macOS下输入位置错乱,Linux下无法输入。手动执行reset可修复,但用os.system('reset')会有冗余提示,os.system('stty sane; clear;')效果不好。以下是相关代码、运行日志,以及问题原因和修复方案:
参考示例代码
# Source: https://stackoverflow.com/a/39989442/20307768 import sys, tempfile, os from subprocess import call EDITOR = os.environ.get('EDITOR', 'vim') # that easy! initial_message = b'something' # if you want to set up the file somehow with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: tf.write(initial_message) tf.flush() call([EDITOR, tf.name])
问题代码
import sys, tempfile, os from subprocess import call EDITOR = os.environ.get('EDITOR', 'vim') text = sys.stdin.read() initial_message = text.encode() with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: tf.write(initial_message) tf.flush() call([EDITOR, tf.name])
运行日志
[rockyos@localhost python-vipe]$ echo "asdfasd" | python vipe.py + python vipe.py + echo asdfasd Vim: Warning: Input is not from a terminal ++ printf '\033]0;%s@%s:%s\007' rockyos localhost '~/TODO/python-vipe' ++ history -a ++ history -c ++ history -r [rockyos@localhost python-vipe]$
终端异常的原因
- 管道执行脚本时,
sys.stdin被重定向为管道输入而非终端(TTY)。Vim检测到标准输入不是终端,会进入非交互模式,但仍会尝试修改终端状态(比如关闭回显、切换到原始模式)。 - 退出Vim后,它没有正确恢复终端原始状态——因为非终端输入环境下,Vim可能不会执行终端状态的清理逻辑,导致终端停留在原始模式或其他异常状态,进而出现输入错乱、无法输入的问题。
- 日志里的
Vim: Warning: Input is not from a terminal提示,也印证了Vim未运行在预期的终端环境中。
无冗余提示的终端恢复方案
方法1:让Vim连接到终端(根源解决)
调用Vim时,强制将其标准输入/输出/错误重定向到当前终端设备(通常是/dev/tty),让Vim认为自己运行在终端环境中,退出时会自动正确恢复终端状态。修改后的代码如下:
import sys, tempfile, os from subprocess import call EDITOR = os.environ.get('EDITOR', 'vim') text = sys.stdin.read() initial_message = text.encode() with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: tf.write(initial_message) tf.flush() # 重定向Vim的输入输出到终端 with open('/dev/tty', 'r+') as tty: call([EDITOR, tf.name], stdin=tty, stdout=tty, stderr=tty)
方法2:手动保存并恢复终端状态(备选)
如果无法让Vim连接到终端,可以用Python的termios模块预先保存终端原始属性,Vim退出后强制恢复,避免reset的冗余提示:
import sys, tempfile, os, termios from subprocess import call EDITOR = os.environ.get('EDITOR', 'vim') # 保存终端原始属性 old_attrs = termios.tcgetattr(sys.stdin) try: text = sys.stdin.read() initial_message = text.encode() with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: tf.write(initial_message) tf.flush() call([EDITOR, tf.name]) finally: # 恢复终端原始属性 termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_attrs)
内容的提问来源于stack exchange,提问作者Constantin Hong
相关产品推荐
相关产品推荐

