linux 如果使用'x-terminal-emulator'启动,则在其他终端中等待子进程可以工作,但如果使用'gnome-terminal'启动,则不能工作

cbwuti44  于 2023-05-16  发布在  Linux
关注(0)|答案(1)|浏览(347)

我的最终目标是让我的python脚本在 another 终端中启动一个子进程,它独立于父进程运行:

  • 当父进程完成时,子进程应该继续。它不应该被杀死。
  • 父进程应该能够等待子进程完成,或者只是继续自己的工作。

我用下面的脚本实现了这个目标。请注意,我选择启动的子进程是python3 child.py,但它可以是其他任何东西(不一定是python进程)。还要注意,在本例中,我通过在最后添加.communicate()来让父进程等待子进程。

# Script parent.py
# ================
import subprocess, shutil

if __name__ == '__main__':
    print('Launch child process')
    p = subprocess.Popen(
        ['x-terminal-emulator', '-e', 'python3', 'child.py'],
    ).communicate()
    print('Child process finished')
    exit(0)

然后启动父进程:

$ python3 parent.py

效果很好。父进程首先打印'Launch child process',然后子进程出现在另一个终端中。孩子做他的事情,然后关闭。* 关闭子进程 * 后,父进程打印'Child process finished'。太好了!
不幸的是,x-terminal-emulator只存在于Debian及其衍生产品上。我的目标是让这种方法在 * 大多数 * Linux系统上工作。
到目前为止,我能找到的最明智的方法是尝试一些默认的“终端模拟器”,它们存在于最常见的Linux发行版中。例如:

  • 'gnome-terminal'
  • 'konsole'
  • 'xterm'
  • 'urxvt'
  • 'rxvt'
  • 'termit'
  • 'terminator'
  • $TERMINAL(这是一个非标准变量)

我尝试使用'gnome-terminal'开始,但它已经出错了:

# Script parent.py
# ================
import subprocess, shutil

if __name__ == '__main__':
    print('Launch child process')
    # For the 'gnome-terminal', the '-e' argument is deprecated.
    # One should use the '--' argument instead.
    p = subprocess.Popen(
        ['gnome-terminal', '--', 'python3', 'child.py'],
    ).communicate()
    print('Child process finished')
    exit(0)

子进程启动了,但是父进程并没有等待它完成--尽管我通过在Popen(..)的末尾添加.communicate()明确地指示它这样做。
换句话说,父进程在子进程实际完成之前立即打印'Child process finished'

问题1:

为什么这种方法适用于'x-terminal-emulator'而不适用于'gnome-terminal'?PS:我正在使用Ubuntu。

  • 编辑:我找到了解决这个问题的方法。必须添加--wait标志来告诉gnome-terminal在其子进程退出之前不要返回。
    问题二:

如何让这种方法在 * 大多数 * Linux系统上工作?如果我尝试启动任何其他终端模拟器,如xtermkonsole,...我是否会遇到类似的问题?我现在不能测试,因为我现在只有Ubuntu。

gopyfrb3

gopyfrb31#

我相信我现在有一个相当好的方法。我列出了Linux中最常见的终端模拟器,并循环查看系统中存在哪一个。然后在该终端中启动子进程。
终端之间有一些细微的差别。我已经在我的Ubuntu上安装了一些。这是我目前掌握的情况:

# parent.py
# =========
import sys, subprocess, shutil, shlex
from typing import *

def __get_terminal() -> Tuple[str, str]:
    '''
    Find a terminal present on this system. Return it as a tuple (terminal_name, terminal_path).
    For example: ('gnome-terminal', '/usr/bin/gnome-terminal').
    '''
    terminal_list = [
        'gnome-terminal', 'x-terminal-emulator', 'xterm', 'konsole', 'xfce4-terminal', 'qterminal',
        'lxterminal', 'alacritty', 'terminator',
    ]
    terminal_name = None
    terminal_path = None
    for terminal in terminal_list:
        if shutil.which(terminal):
            terminal_name = terminal
            terminal_path = shutil.which(terminal)
            break
        continue
    else:
        raise RuntimeError('No terminal found!')
    return terminal_name, terminal_path

def __launch_in_terminal(program:str, argv:List[str]) -> None:
    '''
    Launch the given program in a terminal and pass it the arguments in argv.
    '''
    terminal_name, terminal_path = __get_terminal()
    print(f'terminal_name = {terminal_name}')
    print(f'terminal_path = {terminal_path}')
    print(f'__launch_in_terminal({program}, {argv})')

    # The 'gnome-terminal' requires a '--wait' argument to let it not return until its child process
    # has completed. Also, this terminal needs the '--' argument instead of '-e', which is depre-
    # cated.
    if terminal_name == 'gnome-terminal':
        p = subprocess.Popen(
            [terminal_path, '--wait', '--', program, *argv],
        )
        p.wait()

    # The 'xfce4-terminal' and 'terminator' terminal emulators don't work if you pass the program
    # and arguments as separate list elements. So you need to join them with shlex.
    elif terminal_name in ('xfce4-terminal', 'terminator'):
        p = subprocess.Popen(
            [terminal_path, '-e', shlex.join([program, *argv])],
        )
        p.wait()

    # For all other terminal emulators, the approach is the same.
    else:
        p = subprocess.Popen(
            [terminal_path, '-e', program, *argv],
        )
        p.wait()
    return

if __name__ == '__main__':
    print('Launch child process')
    # Launch the child process in another terminal, and pass it the arguments that were given to
    # this parent process. To make a demonstration, we choose the child process to be a python
    # interpreter running the 'child.py' script.
    __launch_in_terminal(
        program = 'python3',
        argv    = ['child.py', *sys.argv[1:]],
    )
    print('Child process finished')
    exit(0)

请让我知道,如果我忘记了其中一个“大”的,或者如果我犯了任何错误,在启动这些终端的一些。
如果您想测试这个脚本,只需将其复制粘贴到parent.py文件中。在同一个文件夹中还需要一个child.py文件。child.py文件可以是任何东西。请随意使用此示例代码:

# child.py
# ========
from typing import *
import sys, os, time, argparse

if __name__ == '__main__':
    print('Start child process')
    print('===================')
    input('Press any key to continue...')
    print('')

    # Is this frozen?
    time.sleep(0.3)
    print('Frozen:'.ljust(20), end='')
    print(getattr(sys, "frozen", False))

    # Print the executable running this script. It will be the Python interpreter in general, or
    # the frozen executable if running from compiled code.
    time.sleep(0.3)
    print('sys.executable:'.ljust(20), end='')
    print(sys.executable.replace('\\', '/'))

    # Print the value of __file__
    time.sleep(0.3)
    print('__file__:'.ljust(20), end='')
    print(__file__.replace('\\', '/'))

    # Print the location of this file. It should be equal to __file__ if the Python interpreter is
    # running this script. If this is running as compiled code, the location of this file differs
    # from the value of __file__.
    file_location:Optional[str] = None
    if getattr(sys, 'frozen', False):
        # Frozen, running as compiled code
        file_location = os.path.realpath(sys.executable).replace('\\', '/')
    else:
        # Running from interpreter
        file_location = os.path.realpath(__file__).replace('\\', '/')
    time.sleep(0.3)
    print('File location:'.ljust(20), end='')
    print(file_location)

    # Print the arguments given to this program
    parser = argparse.ArgumentParser(description='Simple program', add_help=False)
    parser.add_argument('-h', '--help', action='store_true')
    parser.add_argument('-f', '--foo',  action='store_true')
    parser.add_argument('-b', '--bar',  action='store')
    args = parser.parse_args()
    print('Arguments:')
    time.sleep(0.3)
    print(f'    --help = {args.help}')
    time.sleep(0.3)
    print(f'    --foo  = {args.foo}')
    time.sleep(0.3)
    print(f'    --bar  = {args.bar}')
    time.sleep(0.3)
    print(f'    sys.argv = {sys.argv}')

    # Exit
    time.sleep(0.3)
    print('')
    input('Press any key to continue...')
    sys.exit(0)

然后像这样运行父对象:

$ python3 parent.py --foo --bar=baz

相关问题