3

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

理想的工作流程是这样的:

  1. 您的 python 脚本正在运行,并调用 my_raw_input()。
  2. Vim(或 emacs、gedit 或任何其他文本编辑器)打开时显示空白文档
  3. 您在文档中键入一些文本,然后保存并退出
  4. python 脚本继续运行,文件的内容作为 my_raw_input() 的返回值。

如果你熟悉 git,这就是使用时的体验git commit,编辑器是通过core.editor配置的。其他实用程序crontab -e也这样做。

最终,我希望这个 my_raw_input() 函数也采用带有默认输入内容的可选字符串,然后用户可以对其进行编辑。

研究至今

  • os.exec用编辑器命令替换当前进程,但不返回。即,当 vim 启动时,您的 python 脚本会退出。
  • popen不以交互方式启动子进程,没有显示用户界面。
  • vim 有一个-命令行参数可以从标准输入读取,但没有任何东西可以用:w.
  • 我查看了git 的代码,我根本无法理解。

这可能吗?

编辑

到目前为止很好的答案。我还发现了做同样事情的mecurial 代码。我还提出了一个通过查看crontab 代码的示例,但与某些响应相比,它看起来不必要地复杂。

#!/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')
4

2 回答 2

9

您将数据写入一个临时文件,然后在编辑器返回时读取它。如果你运行git commit,你会注意到 git 正在做同样的事情。

交互式启动程序没有额外的步骤,只要子进程具有stdinstdout连接到终端,它将是交互式的。

使用编辑器有一个陷阱——他们中的许多人会通过在同一目录中写入一个临时文件并将其移动到旧文件上来保存文件。这使得保存操作完全是原子的(忽略电源可能会断电),但这意味着我们必须在编辑器运行后重新打开临时文件,因为我们的旧文件句柄将指向一个不再属于文件系统(但它仍在磁盘上)。

这个问题意味着我们不能使用TemporaryFileor 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)

subprocessGit 示例代码非常复杂,因为它没有使用像 Python模块这样的高级库。如果您阅读subprocess模块源代码,它的大部分内容看起来就像链接的 Git 源代码(用 Python 而不是 C 编写的除外)。

于 2012-10-31T22:16:08.930 回答
1

您必须创建一个临时文件名,以便编辑器存储其内容。您可以使用tempfile.mkstemp()它。如果您想在该文件中放入一些内容,您可以这样做。

对于运行该命令,subprocess.check_call()似乎是该作业的正确工具,因为 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

您当然可以自定义要使用的编辑器等等。

于 2012-10-31T22:21:22.530 回答