使用可配置文本编辑器的Python raw_input()替换

使用可配置文本编辑器的Python raw_input()替换,python,Python,我正在尝试实现一个对raw_input()的替换,它将使用一个可配置的文本编辑器(如vim)作为用户的界面 理想的工作流程如下所示: 您的python脚本正在运行,并调用my_raw_input() Vim(或emacs、或gedit或任何其他文本编辑器)打开空白文档 在文档中键入一些文本,然后保存并退出 python脚本恢复运行,文件内容作为my_raw_input()的返回值 如果您熟悉git,这就是使用git commit时的体验,其中编辑器是通过配置的。其他实用程序,如crontab-e

我正在尝试实现一个对raw_input()的替换,它将使用一个可配置的文本编辑器(如vim)作为用户的界面

理想的工作流程如下所示:

  • 您的python脚本正在运行,并调用my_raw_input()
  • Vim(或emacs、或gedit或任何其他文本编辑器)打开空白文档
  • 在文档中键入一些文本,然后保存并退出
  • python脚本恢复运行,文件内容作为my_raw_input()的返回值
  • 如果您熟悉git,这就是使用
    git commit
    时的体验,其中编辑器是通过配置的。其他实用程序,如
    crontab-e
    也可以这样做

    最后,我希望这个my_raw_input()函数还可以获取一个可选字符串,其中包含默认的输入内容,然后用户可以对其进行编辑

    迄今为止的研究
    • 用编辑器命令替换当前进程,但不返回。也就是说,当vim启动时,您的python脚本将退出
    • 不以交互方式启动子进程,没有显示用户界面
    • vim有一个
      -
      命令行参数可从stdin读取,但没有任何参数可通过
      :w
      写入stdout
    • 我看了一眼,我完全看不懂
    这可能吗

    编辑 到目前为止,答案不错。我还发现了做同样事情的人。我还提出了一个例子,可以通过查看答案来工作,但与一些回答相比,它看起来是不必要的复杂

    #!/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
    抛出,您将