使用可配置文本编辑器的Python raw_input()替换
我正在尝试实现一个对raw_input()的替换,它将使用一个可配置的文本编辑器(如vim)作为用户的界面 理想的工作流程如下所示:使用可配置文本编辑器的Python raw_input()替换,python,Python,我正在尝试实现一个对raw_input()的替换,它将使用一个可配置的文本编辑器(如vim)作为用户的界面 理想的工作流程如下所示: 您的python脚本正在运行,并调用my_raw_input() Vim(或emacs、或gedit或任何其他文本编辑器)打开空白文档 在文档中键入一些文本,然后保存并退出 python脚本恢复运行,文件内容作为my_raw_input()的返回值 如果您熟悉git,这就是使用git commit时的体验,其中编辑器是通过配置的。其他实用程序,如crontab-e
git commit
时的体验,其中编辑器是通过配置的。其他实用程序,如crontab-e
也可以这样做
最后,我希望这个my_raw_input()函数还可以获取一个可选字符串,其中包含默认的输入内容,然后用户可以对其进行编辑
迄今为止的研究
- 用编辑器命令替换当前进程,但不返回。也就是说,当vim启动时,您的python脚本将退出
- 不以交互方式启动子进程,没有显示用户界面
- vim有一个
命令行参数可从stdin读取,但没有任何参数可通过-
写入stdout:w
- 我看了一眼,我完全看不懂李>
#!/usr/bin/python
import os
import tempfile
def raw_input_editor(default=None, editor=None):
''' like the built-in raw_input(), except that it uses a visual
text editor for ease of editing. Unline raw_input() it can also
take a default value. '''
editor = editor or get_editor()
with tempfile.NamedTemporaryFile(mode='r+') as tmpfile:
if default:
tmpfile.write(default)
tmpfile.flush()
child_pid = os.fork()
is_child = child_pid == 0
if is_child:
os.execvp(editor, [editor, tmpfile.name])
else:
os.waitpid(child_pid, 0)
tmpfile.seek(0)
return tmpfile.read().strip()
def get_editor():
return (os.environ.get('VISUAL')
or os.environ.get('EDITOR')
or 'vi')
if __name__ == "__main__":
print raw_input_editor('this is a test')
将数据写入临时文件,然后在编辑器返回时读取。如果运行
gitcommit
,您会注意到git正在做同样的事情
以交互方式启动程序没有额外步骤,只要子进程将stdin
和stdout
连接到终端,它将是交互的
使用编辑器有一个难题——许多编辑器将通过在同一目录中写入临时文件并将其移到旧文件上来保存文件。这使保存操作完全原子化(忽略电源可能会熄灭),但这意味着我们必须在编辑器运行后重新打开临时文件,因为旧的文件句柄将指向不再是文件系统一部分的文件(但它仍在磁盘上)
这意味着我们不能使用TemporaryFile
或NamedTemporaryFile
,我们必须使用较低级别的工具,以便在不删除文件的情况下关闭文件描述符并重新打开文件
import tempfile
import subprocess
import os
def edit(data):
fdes = -1
path = None
fp = None
try:
fdes, path = tempfile.mkstemp(suffix='.txt', text=True)
fp = os.fdopen(fdes, 'w+')
fdes = -1
fp.write(data)
fp.close()
fp = None
editor = (os.environ.get('VISUAL') or
os.environ.get('EDITOR') or
'nano')
subprocess.check_call([editor, path])
fp = open(path, 'r')
return fp.read()
finally:
if fp is not None:
fp.close()
elif fdes >= 0:
os.close(fdes)
if path is not None:
try:
os.unlink(path)
except OSError:
pass
text = edit('Hello, World!')
print(text)
Git示例代码非常复杂,因为它没有使用Python的
子流程
模块这样的高级库。如果您阅读子流程
模块源代码,其中的大块代码将看起来像链接的Git源代码(除了用Python而不是C编写的代码)。您必须创建一个临时文件名,以便编辑器将其内容存储在其中。你可以用它。如果您想在该文件中放入一些内容,可以这样做
对于运行该命令,似乎是作业的正确工具,因为python会等待该命令返回,并且在子流程失败时引发异常。大致:
import os
import tempfile
import subprocess
def my_raw_input(default=''):
tf, tn = tempfile.mkstemp()
os.close(tf)
with open(tn) as tf:
tf.write(default)
rv = subprocess.check_call(['emacs', tn])
with open(tn) as f:
data = f.read()
os.unlink(tn)
return data
当然,您可以自定义要使用的编辑器,等等。您只需编写一个临时文件和fork,然后执行。@Keith:不需要用Python自己编写
fork
和exec,这就是子流程
模块的用途。@Keith:真的吗?您发现fork
和exec
比Popen
更容易?如何关闭所有不需要的文件句柄?@Keith:听起来你自己没有使用fork
和exec
,而是使用你编写的库,它使用这些函数复制子流程
模块中的功能,编写这个库比使用自2004年就存在的现有库更容易?这是一个大胆的主张(您的代码比python代码更具python风格?)。尽管如此,当此站点的读者无法访问封装在更高级别对象中的类时,最好推荐使用子流程而不是fork/exec-madness。但您可能应该将f
放入带有块的中(我认为tempfile
是自上下文的;如果不是,则关闭应该可以);现有代码在抛出时不会关闭f
。另外,你不能用子流程来简化一下这个过程。检查\u call
?编辑器环境变量的使用很好-当工具不支持此设置时,它总是令人恼火。@abarnert:人们在野外使用视觉
?我认为它基本上已经不存在了,现在只不过是编辑器的别名,因为世界上只有两个人使用非可视化编辑器。如果DISPLAY
未设置,则emacs
和gvim
工作正常。@abarnert:“非可视化编辑器”指的是ed
等编辑器,而不是vi
和emacs
等编辑器。使用可视是非标准的。如果要启动单独的编辑器,请使用包装脚本#/如果测试-z“$DISPLAY”,则箱子/sh;然后执行EmacClient“$@”;else exec emacs“$@”;fi
,我不明白为什么每个应用程序都要做这个测试,因为它一开始是不可移植的。@abarnert:我也想知道哪个版本的Git可以做这个测试,因为1.7没有(它总是使用可视化而不是编辑器)。看。我认为使用tempfile
比使用os.tempnam
更好。此外,它也不会执行OP请求的“获取可选字符串和默认输入内容”。是的,tempfile更好。修正了这个问题。好的,但是tempfile.NamedTemporaryFile
仍然比tempfile.mkstemp
好。例如,如果check\u call
抛出,您将