Стоп Подоболочка bash, когда родительский процесс требует ввода tty

Вы можете вывести значение по умолчанию, если grep не работает:

var=$( grep -e '^setting_1' || echo default_setting )

Обратите внимание, что вы не закрывали кавычки (и без аргумента файла grep будет фильтровать стандартный ввод).

1
08.04.2019, 03:23
1 ответ

Если я правильно понял ваше описание, осмелюсь предположить, что wrapperпрограмма не предназначена для порождения интерактивных дочерних элементов, иначе она позаботится об остановке (SIGSTOP )своих дочерних элементов перед доступом к tty, а затем возобновляя их (SIGCONT ), как только он закончит работу с tty. По-видимому, это не так, если он ожидает, что ему будет разрешен доступ к tty произвольно.

Можно довольно легко создать вспомогательную программу, которую можно поместить между вашими SHLVL=1 и wrapper, действуя таким образом в качестве буферного слоя между ними, чтобы ваша первая оболочка не обнаруживала остановку wrapper; затем эта вспомогательная программа обнаружит, когда wrapperбудет остановлен, и в таком случае она остановит дочерние элементы wrapper, вернет tty wrapperи возобновит его. Но тогда было бы не так просто обнаружить, когда wrapper закончил работу с tty без активного сотрудничества (, то есть уведомления )самого wrapper. На самом деле, учитывая описанное поведение, я подозреваю, что wrapperна самом деле не переходит в фоновый режим и не делает ничего, кроме того, что просто засыпает при каком-то блокирующем системном вызове -.

Однако, если он действительно находится в фоновом режиме, то лучшее, что вы можете сделать, это заставить вспомогательную программу постоянно опрашивать tty о текущем активном процессе, и когда он снова становится дочерним wrapper, вспомогательная программа возобновляет его (, если wrapper не делает этого сама, как я подозреваю ).

То есть, вообще, для возобновления ребенка,Боюсь, вам нужно какое-то определенное событие (или последовательность событий ), обнаруживаемая снаружи wrapper, с помощью которой вы можете правильно сделать вывод, что wrapperдействительно завершил работу с tty, и по такому событию (s )резюме wrappers ребенок.

Для случая, когда разумно иметь решение, при котором вы вручную возобновляете дочерний элемент wrapper, вот пример программы Python, которая должна обрабатывать конкретный случай:

#!/usr/bin/python3

import os
import sys
import signal


def main():
    if len(sys.argv) < 2:
        sys.exit(0)

    def _noop_handler(sig, frame):
        """signal handler that does nothing"""
        pass

    termination_signals = {signal.SIGHUP, signal.SIGINT, signal.SIGTERM}
    management_signals = {signal.SIGCHLD, signal.SIGCONT, signal.SIGTTIN,
                          signal.SIGUSR1, signal.SIGUSR2}
    signal.pthread_sigmask(
            signal.SIG_BLOCK,
            management_signals | termination_signals
    )

    child = os.fork()

    if child == 0:  # child process after fork
        signal.sigwait({signal.SIGUSR1})  # wait go-ahead signal from parent
        signal.pthread_sigmask(
                signal.SIG_UNBLOCK,
                management_signals | termination_signals
        )
        os.execvp(sys.argv[1], sys.argv[1:])  # run command
    elif child > 0:  # parent process after fork
        # I want to manipulate tty ownership freely, so ignore SIGTTOU
        signal.signal(signal.SIGTTOU, signal.SIG_IGN)
        # A handler for SIGCHLD is required on some systems where semantics
        # for ignored signals is to never deliver them even to sigwait(2)
        signal.signal(signal.SIGCHLD, _noop_handler)

        in_fd = sys.stdin.fileno()
        my_pid = os.getpid()
        ppid = os.getppid()
        os.setpgid(child, child)  # put child in its own process group
        if os.tcgetpgrp(in_fd) == my_pid:
            # if I have been given the tty, hand it over to child
            # This is not the case when shell spawned me in "background" &
            os.tcsetpgrp(in_fd, child)
        os.kill(child, signal.SIGUSR1)  # all set for child, make it go ahead
        last_robbed_group = 0
        # signals to care for child
        io_wanted_signals = {signal.SIGTTIN, signal.SIGTTOU}

        def _send_sig(_pgid, _sig, accept_myself=False) -> bool:
            """
            send a signal to a process group if that is not my own or
            if accept_myself kwarg is True, and ignore OSError exceptions
            """
            if not accept_myself and _pgid == my_pid:
                return True
            try:
                os.killpg(_pgid, _sig)
            except OSError:
                return False
            return True

        def _resume_child_if_appropriate():
            """
            resume child unless that would steal tty from my own parent
            """
            nonlocal last_robbed_group
            fg_group = os.tcgetpgrp(in_fd)
            if fg_group == os.getpgid(ppid):
                # Minimal protection against stealing tty from parent shell.
                # If this would be the case, rather stop myself too
                _send_sig(my_pid, signal.SIGTTIN, accept_myself=True)
                return
            # Forcibly stop current tty owner
            _send_sig(fg_group, signal.SIGSTOP)
            if fg_group not in {os.getpgid(child), my_pid}:
                # remember who you stole tty from
                last_robbed_group = fg_group
            # Resume child
            os.tcsetpgrp(in_fd, os.getpgid(child))
            _send_sig(os.getpgid(child), signal.SIGCONT)

        waited_signals = termination_signals | management_signals
        while True:
            # Blocking loop over wait for signals
            sig = signal.sigwait(waited_signals)
            if sig in termination_signals:
                # Propagate termination signal and then exit
                _send_sig(os.getpgid(child), sig)
                os.wait()
                sys.exit(128 + sig)
            elif sig == signal.SIGCONT:
                # CONT received, presumably from parent shell, propagate it
                _resume_child_if_appropriate()
            elif sig == signal.SIGTTIN:
                # TTIN received, presumably from myself
                prev_fg = os.tcgetpgrp(in_fd)
                # Stop current tty owner if not my own parent
                if prev_fg != os.getpgid(ppid):
                    _send_sig(prev_fg, signal.SIGSTOP)
                try:
                    # Give tty back to my own parent and stop myself
                    os.tcsetpgrp(in_fd, os.getpgid(ppid))
                    _send_sig(my_pid, signal.SIGSTOP, accept_myself=True)
                except OSError:
                    try:
                        # ugh, parent unreachable, restore things
                        os.tcsetpgrp(in_fd, prev_fg)
                        _send_sig(prev_fg, signal.SIGCONT)
                    except OSError:
                        # Non-restorable situation ? let's idle then
                        os.tcsetpgrp(in_fd, my_pid)
            elif sig == signal.SIGCHLD:
                # Event related to child, let's investigate it
                pid, status = os.waitpid(child, os.WNOHANG | os.WUNTRACED)
                if pid > 0:
                    if os.WIFSIGNALED(status):
                        # Child terminated by signal, let's propagate this
                        sys.exit(128 + os.WTERMSIG(status))
                    elif os.WIFEXITED(status):
                        # Child exited normally, let's propagate this
                        sys.exit(os.WEXITSTATUS(status))
                    elif os.WIFSTOPPED(status) and \
                            os.WSTOPSIG(status) in io_wanted_signals:
                        # Child got stopped trying to access the tty, resume it
                        _resume_child_if_appropriate()
            elif sig in {signal.SIGUSR1, signal.SIGUSR2} \
                    and last_robbed_group:
                # Management signals to resume robbed process
                if sig == signal.SIGUSR2:
                    # Forcibly stop child, whatever it is doing or not doing
                    _send_sig(os.getpgid(child), signal.SIGSTOP)
                try:
                    # resume robbed process
                    os.tcsetpgrp(in_fd, last_robbed_group)
                    os.killpg(last_robbed_group, signal.SIGCONT)
                except OSError:
                    # Robbed process no longer exists ? oh well..
                    last_robbed_group = 0
                    try:
                        # resume child then
                        os.tcsetpgrp(in_fd, os.getpgid(child))
                        os.killpg(os.getpgid(child), signal.SIGCONT)
                    except OSError:
                        pass


if __name__ == '__main__':
    main()

Требуется как минимум Python версии 3.3.

Он не очень хорошо спроектирован :он состоит из одной основной функции с парой вспомогательных -функций, но цель состоит в том, чтобы быть как можно более читабельным и понятным, при этом предоставляя основной набор необходимых функций..

Кроме того, это может быть расширено, например, до. хорошо работать с оболочками, которые не являются его прямыми родителями, или с возможными рекурсивными вызовами одной и той же программы, или также с возможными условиями гонки при запросе текущего процесса переднего плана и последующем его изменении, и, возможно, в других крайних случаях.

Вышеупомянутая программа автоматически останавливает текущего владельца tty и возобновляет wrapperработу, если она была остановлена ​​из-за доступа к tty, когда это было запрещено. Чтобы вручную возобновить работу предыдущего владельца tty, я предусмотрел два варианта:

  1. мягкое возобновление :предпочтительнее, если вы уверены, что wrapperзакончили работу с tty; отправьте SIGUSR1 вспомогательной программе -, и она просто возобновит работу предыдущего владельца tty
  2. жесткое возобновление :способ использовать, когда вы хотите остановить wrapperв любом случае; отправьте SIGUSR2 вспомогательной программе -, и она выполнит SIGSTOP wrapperперед возобновлением предыдущего владельца tty
  3. .

Вы также можете отправить SIGCONT вспомогательной -программе :, она принудительно остановит текущего владельца tty и возобновит wrapperнезависимо от этого.

При такой настройкев общем, вам лучше избегать отправки сигналов STOP/CONT непосредственно либо wrapper, либо любому из его дочерних элементов или подчиненных -.

Во всех случаях всегда помните, что вы играете с тем, что обычно представляет собой тонкое взаимодействие между «чужими» программами и их контролируемыми заданиями, особенно когда вы вызываете интерактивные оболочки внутри интерактивных оболочек. Им часто не нравится, когда сигналы SIGSTOP -и SIGCONT -устанавливаются произвольно. Поэтому вам часто нужно тщательно применять правильную последовательность операций, чтобы они не реагировали внезапным выходом или испорченным окном терминала.

0
28.01.2020, 00:16

Теги

Похожие вопросы