Python3/Linux – открыть текстовый файл в редакторе по умолчанию и дождаться завершения

Мне нужно дождаться, пока пользователь закончит редактирование текстового файла в графическом приложении по умолчанию (Debian и его производные).

Если я использую xdg-open с subprocess.call (который обычно ждет), он продолжится после открытия файла в редакторе. Я предполагаю, что сам xdg-open запускает редактор асинхронно.

Наконец-то я получил более-менее работающий код, получив пусковую установку для текстового/простого MIME-типа и используя его с Gio.DesktopAppInfo.new, чтобы получить команду для редактора. При условии, что редактор еще не открыт, и в этом случае процесс завершается, пока редактор еще открыт.

Я добавил решения для проверки process.pid и опроса процесса. Оба заканчиваются неопределенной петлей.

Кажется, это слишком сложный способ дождаться завершения процесса. Итак, есть ли более надежный способ сделать это?

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

person Arjen Balfoort    schedule 02.01.2021    source источник
comment
Вы спрашиваете конкретно о решении, использующем xdg-open? Или вы ищете решение, которое позволяет избежать xdg-open? Также, возможно, стоит явно указать, на каких операционных системах вы хотите, чтобы решение работало.   -  person Shane Bishop    schedule 05.01.2021
comment
xdg-open, вероятно, излишне; можно просто позвонить os.environ.get('VISUAL', os.envrion.get('EDITOR', 'vi'))?   -  person tripleee    schedule 05.01.2021
comment
Мне нужно дождаться, пока пользователь закончит редактирование текстового файла в графическом приложении по умолчанию. Я добавил ОС (Debian и производные) в OP. xdg-open не является обязательным условием, однако в этих системах обычным способом является открытие файлов в редакторе по умолчанию. Приложение представляет собой графическое приложение, а не терминальное приложение. Итак, явный вызов редактора по умолчанию с помощью os.eviron — это не то, что мне нужно. Я обновил ОП, чтобы отразить это.   -  person Arjen Balfoort    schedule 05.01.2021
comment
В каком текстовом редакторе вы тестируете? Это было бы полезно знать всем, кто пытается ответить, чтобы они могли проверить, работает ли их решение.   -  person Shane Bishop    schedule 05.01.2021
comment
В моем случае: kate (plasma5) и коврик для мыши (xfce4).   -  person Arjen Balfoort    schedule 07.01.2021


Ответы (1)


Вместо того, чтобы пытаться опросить PID, вы можете просто дождаться завершения дочернего процесса, используя subprocess.Popen.wait():

Дождитесь завершения дочернего процесса. Установить и вернуть атрибут кода возврата.

Кроме того, не гарантируется, что первая часть get_commandline() будет лаунчером. Строка, возвращаемая get_commandline(), будет соответствовать спецификации ключа Exec, это означает, что коды полей %u, %U, %f и %F в возвращаемой строке должны быть заменены правильными значениями.

Вот пример кода, основанный на вашем подходе 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

У меня есть несколько замечаний по моему примеру кода.

Примечание 1. Обработка пробелов в путях к файлам

Важно, чтобы путь к открываемому файлу был заключен в кавычки, иначе shlex.split(command) разделит имя файла на пробелы.

Примечание 2. Экранированные % символов

В Execключевой спецификации указано

Символы буквального процента должны экранироваться как %%.

Мое использование replace() потенциально может заменить % символов, которые были экранированы. Для простоты я решил проигнорировать этот пограничный случай.

Примечание 3: атом

Я предположил, что желаемое поведение — всегда ждать закрытия графического редактора. В случае текстового редактора atom он сразу же завершится при запуске окна, если не указана опция --wait. По этой причине я условно добавляю параметр --wait, если редактором по умолчанию является атом.

Примечание 4: subprocess.DEVNULL

subprocess.DEVNULL появился в Python 3.3. Для более старых версий Python вместо этого можно использовать следующее:

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

Тестирование

Я протестировал приведенный выше пример кода на Ubuntu со средой рабочего стола GNOME. Я тестировал следующие графические текстовые редакторы: gedit, коврик для мыши и atom.

person Shane Bishop    schedule 04.01.2021
comment
Спасибо, но, к сожалению, это также открывает редактор и немедленно завершает процесс. Отпечатки открытия и закрытия печатаются, пока файл все еще редактируется в редакторе по умолчанию. Я добавил это решение в OP. - person Arjen Balfoort; 05.01.2021
comment
@ArjenBalfoort Я обновил свой ответ, чтобы работать с ковриком для мыши. Не могли бы вы взглянуть еще раз? - person Shane Bishop; 07.01.2021
comment
Отлично работает, в том числе и с Кейт! Я исключаю это как ответ. Я также хотел бы узнать ваше мнение о моем обходном пути с использованием ps -o pid, cmd -e. Видите ли вы недостатки по сравнению с вашим решением? - person Arjen Balfoort; 08.01.2021
comment
Я не думаю, что понимаю. Почему вызов ps является действием опроса, разве он не вызывается только один раз? В таком случае, не является ли вызов xdg-mime также действием опроса? В очередной раз благодарим за помощь. - person Arjen Balfoort; 09.01.2021
comment
Извините, я не очень хорошо объяснил. Вы правы, вы выполняете ps только один раз, но затем у вас есть цикл while, в котором вы постоянно вызываете check_pid(), и вы выходите из цикла, только если возвращаемое значение check_pid() равно ложь. Непрерывная проверка условия называется опросом. - person Shane Bishop; 09.01.2021
comment
Благодарю за разъяснение. Я раньше не слышал о ложном/истинном значении опроса. Никогда не стареть, чтобы учиться. - person Arjen Balfoort; 10.01.2021