简体   繁体   English

Python3/Linux - 在默认编辑器中打开文本文件并等待完成

[英]Python3/Linux - Open text file in default editor and wait until done

I need to wait until the user is done editing a text file in the default graphical application (Debian and derivates).我需要等到用户在默认图形应用程序(Debian 和衍生产品)中编辑完文本文件。

If I use xdg-open with subprocess.call (which usually waits) it will continue after opening the file in the editor.如果我将 xdg-open 与 subprocess.call 一起使用(通常会等待),它将在编辑器中打开文件后继续。 I assume because xdg-open itself starts the editor asynchronously.我假设是因为 xdg-open 本身异步启动编辑器。

I finally got a more or less working code by retrieving the launcher for the text/plain mime-type and use that with Gio.DesktopAppInfo.new to get the command for the editor.通过检索 text/plain mime 类型的启动器并将其与 Gio.DesktopAppInfo.new 一起使用来获取编辑器的命令,我终于获得了或多或少的工作代码。 Provided that the editor is not already open in which case the process ends while the editor is still open.假设编辑器尚未打开,在这种情况下,该过程在编辑器仍处于打开状态时结束。

I have added solutions checking the process.pid and polling for the process.我添加了检查 process.pid 和轮询进程的解决方案。 Both end in an indefinite loop.两者都以无限循环结束。

It seems such a overly complicated way to wait for the process to finish.等待过程完成似乎是一种过于复杂的方式。 So, is there a more robust way to do this?那么,有没有更强大的方法来做到这一点?

#! /usr/bin/env python3

import subprocess
from gi.repository import Gio
import os
from time import sleep
import sys


def open_launcher(my_file):
    print('launcher open')
    app = subprocess.check_output(['xdg-mime', 'query', 'default', 'text/plain']).decode('utf-8').strip()
    print(app)
    launcher = Gio.DesktopAppInfo.new(app).get_commandline().split()[0]
    print(launcher)
    subprocess.call([launcher, my_file])
    print('launcher close')
    
def open_xdg(my_file):
    print('xdg open')
    subprocess.call(['xdg-open', my_file])
    print('xdg close')
    
def check_pid(pid):        
    """ Check For the existence of a unix pid. """
    try:
        os.kill(int(pid), 0)
    except OSError:
        return False
    else:
        return True
    
def open_pid(my_file):
    pid = subprocess.Popen(['xdg-open', my_file]).pid
    while check_pid(pid):
        print(pid)
        sleep(1)
        
def open_poll(my_file):
    proc = subprocess.Popen(['xdg-open', my_file])
    while not proc.poll():
        print(proc.poll())
        sleep(1)
        
def open_ps(my_file):
    subprocess.call(['xdg-open', my_file])
    pid = subprocess.check_output("ps -o pid,cmd -e | grep %s | head -n 1 | awk '{print $1}'" % my_file, shell=True).decode('utf-8')
    while check_pid(pid):
        print(pid)
        sleep(1)
        
def open_popen(my_file):
    print('popen open')
    process = subprocess.Popen(['xdg-open', my_file])
    process.wait()
    print(process.returncode)
    print('popen close')


# This will end the open_xdg function while the editor is open.
# However, if the editor is already open, open_launcher will finish while the editor is still open.
#open_launcher('test.txt')

# This solution opens the file but the process terminates before the editor is closed.
#open_xdg('test.txt')

# This will loop indefinately printing the pid even after closing the editor.
# If you check for the pid in another terminal you see the pid with: [xdg-open] <defunct>.
#open_pid('test.txt')

# This will print None once after which 0 is printed indefinately: the subprocess ends immediately.
#open_poll('test.txt')

# This seems to work, even when the editor is already open.
# However, I had to use head -n 1 to prevent returning multiple pids.
#open_ps('test.txt')

# Like open_xdg, this opens the file but the process terminates before the editor is closed.
open_popen('test.txt')

Instead of trying to poll a PID, you can simply wait for the child process to terminate, using subprocess.Popen.wait() :您可以使用subprocess.Popen.wait()简单地等待子进程终止,而不是尝试轮询 PID:

Wait for child process to terminate.等待子进程终止。 Set and return returncode attribute.设置并返回 returncode 属性。

Additionally, getting the first part of get_commandline() is not guaranteed to be the launcher.此外,获取get_commandline()的第一部分并不能保证是启动器。 The string returned by get_commandline() will match the Exec key spec , meaning the %u , %U , %f , and %F field codes in the returned string should be replaced with the correct values. get_commandline()返回的字符串将匹配Exec key spec ,这意味着返回字符串中的%u%U%f%F字段代码应替换为正确的值。

Here is some example code, based on your xdg-mime approach:以下是一些示例代码,基于您的xdg-mime方法:

#!/usr/bin/env python3
import subprocess
import shlex
from gi.repository import Gio

my_file = 'test.txt'

# Get default application
app = subprocess.check_output(['xdg-mime', 'query', 'default', 'text/plain']).decode('utf-8').strip()

# Get command to run
command = Gio.DesktopAppInfo.new(app).get_commandline()

# Handle file paths with spaces by quoting the file path
my_file_quoted = "'" + my_file + "'"

# Replace field codes with the file path
# Also handle special case of the atom editor
command = command.replace('%u', my_file_quoted)\
    .replace('%U', my_file_quoted)\
    .replace('%f', my_file_quoted)\
    .replace('%F', my_file_quoted if app != 'atom.desktop' else '--wait ' + my_file_quoted)

# Run the default application, and wait for it to terminate
process = subprocess.Popen(
    shlex.split(command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
process.wait()

# Now the exit code of the text editor process is available as process.returncode

I have a few remarks on my sample code.我对我的示例代码有几点意见。

Remark 1: Handling spaces in file paths备注 1:处理文件路径中的空格

It is important the file path to be opened is wrapped in quotes, otherwise shlex.split(command) will split the filename on spaces.重要的是要打开的文件路径用引号引起来,否则shlex.split(command)会将文件名拆分为空格。

Remark 2: Escaped % characters备注 2:转义%字符

The Exec key spec states Exec关键规范指出

Literal percentage characters must be escaped as %%.文字百分比字符必须转义为 %%。

My use of replace() then could potentially replace % characters that were escaped.我对replace()的使用可能会替换%被转义的字符。 For simplicity, I chose to ignore this edge case.为简单起见,我选择忽略这种边缘情况。

Remark 3: atom备注3:原子

I assumed the desired behaviour is to always wait until the graphical editor has closed.我假设所需的行为是始终等到图形编辑器关闭。 In the case of the atom text editor, it will terminate immediately on launching the window unless the --wait option is provided.对于atom文本编辑器,它将在启动 window 时立即终止,除非提供了--wait选项。 For this reason, I conditionally add the --wait option if the default editor is atom.出于这个原因,如果默认编辑器是 atom,我有条件地添加--wait选项。

Remark 4: subprocess.DEVNULL备注4: subprocess.DEVNULL

subprocess.DEVNULL is new in python 3.3. subprocess.DEVNULL是 python 3.3 中的新功能。 For older python versions, the following can be used instead:对于较旧的 python 版本,可以使用以下版本:

with open(os.devnull, 'w') as DEVNULL:
    process = subprocess.Popen(
        shlex.split(command), stdout=DEVNULL, stderr=DEVNULL)

Testing测试

I tested my example code above on Ubuntu with the GNOME desktop environment.我使用 GNOME 桌面环境在 Ubuntu 上测试了上面的示例代码。 I tested with the following graphical text editors: gedit , mousepad , and atom .我使用以下图形文本编辑器进行了测试: geditmousepadatom

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM