Solución:
He agregado código para que:
interact() {
pid=$1
! ps -p $pid && return
ls -ld /proc/$pid/fd/*
sleep 5; kill -1 $pid # TEST SIGNAL TO PARENT
}
run() {
exec {in}<&0 {out}>&1 {err}>&2
{ coproc "[email protected]" 0<&$in 1>&$out 2>&$err; } 2>/dev/null
exec {in}<&- {out}>&- {err}>&-
{ interact $! <&- >/tmp/whatever.log 2>&1& } 2>/dev/null
fg %1 >/dev/null 2>&1
wait 2>/dev/null
}
los fg %1
se ejecutará para todos los comandos (cambiar %1
según sea necesario para trabajos simultáneos) y, en circunstancias normales, sucederá una de dos cosas:
interact()
volverá inmediatamente ya que no hay nada que hacer y el fg
no hará nada.interact()
puede interactuar (por ejemplo, enviar un HUP al coproceso después de 5 segundos) y el fg
pondrá el coproceso en primer plano usando el mismo stdin / out / err con el que se ejecutó originalmente (puede verificar esto con ls -l /proc/<pid>/df
).
Las redirecciones a / dev / null en los últimos tres comandos son cosméticas. Ellos permiten run <command>
para que parezca exactamente igual que si hubieras corrido command
por sí mismo.
En su código de ejemplo, Vim es suspendido por el kernel a través de una señal SIGTTIN tan pronto como intenta leer desde el tty, o posiblemente establecerle algunos atributos.
Esto se debe a que el shell interactivo lo genera en un grupo de procesos diferente sin (todavía) entregar el tty a ese grupo, es decir, ponerlo “en segundo plano”. Este es un comportamiento de control de trabajo normal, y la forma normal de entregar el tty es usar fg
. Luego, por supuesto, es el caparazón el que pasa a un segundo plano y, por lo tanto, se suspende.
Todo esto es a propósito cuando un shell es interactivo; de lo contrario, sería como si se le permitiera seguir escribiendo comandos en el indicador mientras, por ejemplo, edita un archivo con Vim.
Podrías solucionarlo fácilmente con solo hacer tu run
funciona un script en su lugar. De esa manera, el shell interactivo lo ejecutaría sincrónicamente sin competir con el tty. Si lo hace, su propio código de ejemplo ya hace todo lo que pide, incluida la interacción simultánea entre sus run
(luego un guión) y el coproc.
Si tenerlo en un script no es una opción, entonces puede ver si otros shells además de Bash permitirían un control más preciso sobre el paso del tty interactivo a los procesos secundarios. Personalmente, no soy un experto en proyectiles más avanzados.
Si realmente debe usar Bash y realmente debe tener esta funcionalidad a través de una función para ser ejecutada por el shell interactivo, entonces me temo que la única salida es hacer un programa auxiliar en un lenguaje que le permita acceder a tcsetpgrp (3) y sigprocmask (2).
El propósito sería hacer en el hijo (su coproc) lo que no se ha hecho en el padre (el shell interactivo) para poder agarrar con fuerza el tty.
Sin embargo, tenga en cuenta que esto se considera explícitamente una mala práctica.
Sin embargo, si no usa diligentemente el tty del shell principal mientras el niño aún lo tiene, es posible que no se produzca ningún daño. Por “no usar” me refiero a que no echo
no printf
no read
hacia / desde el tty, y ciertamente no ejecute otros programas que puedan acceder al tty mientras el niño aún se está ejecutando.
Un programa de ayuda en Python podría ser algo como esto:
#!/usr/bin/python3
import os
import sys
import signal
def main():
in_fd = sys.stdin.fileno()
if os.isatty(in_fd):
oldset = signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGTTIN, signal.SIGTTOU})
os.tcsetpgrp(in_fd, os.getpid())
signal.pthread_sigmask(signal.SIG_SETMASK, oldset)
if len(sys.argv) > 1:
# Note: here I used execvp for ease of testing. In production
# you might prefer to use execv passing it the command to run
# with full path produced by the shell's completion
# facility
os.execvp(sys.argv[1], sys.argv[1:])
if __name__ == '__main__':
main()
Su equivalente en C sería solo un poco más largo.
Este programa auxiliar debería ser ejecutado por su coproc con un ejecutivo, como este:
run() {
exec {in}<&0 {out}>&1 {err}>&2
{ coproc exec grab-tty.py "[email protected]" {side_channel_in}<&0 {side_channel_out}>&1 0<&${in}- 1>&${out}- 2>&${err}- ; } 2>/dev/null
exec {in}<&- {out}>&- {err}>&-
# while child running:
# status/signal/exchange data with child process
wait
}
Esta configuración me funcionó en Ubuntu 14.04 con Bash 4.3 y Python 3.4 para todos sus casos de ejemplo, obteniendo la función de mi shell interactivo principal y ejecutándola run
desde el símbolo del sistema.
Si necesita ejecutar un script desde coproc, podría ser necesario ejecutarlo con bash -i
, de lo contrario, Bash podría comenzar con tuberías o / dev / null en stdin / stdout / stderr en lugar de heredar el tty capturado por el script de Python. Además, lo que sea que ejecute dentro del coproc (o debajo de él) será mejor que no invoque adicionales run()
s. (no estoy seguro en realidad, no he probado ese escenario, pero supongo que necesitaría al menos una encapsulación cuidadosa).
Para responder a sus (sub) preguntas específicas, necesito presentar un poco de teoría.
Cada tty tiene una, y solo una, llamada “sesión”. (Sin embargo, no todas las sesiones tienen un tty, como en el caso del proceso de demonio típico, pero supongo que esto no es relevante aquí).
Básicamente, cada sesión es una colección de procesos y se identifica a través de un id correspondiente al pid del “líder de sesión”. El “líder de sesión” es, pues, uno de esos procesos pertenecientes a esa sesión, y precisamente el que primero inició esa sesión concreta.
Todos Los procesos (líder y no) de una sesión en particular pueden acceder al tty asociado a la sesión a la que pertenecen. Pero aquí viene la primera distinción: sólo uno proceso en cualquier momento dado puede ser el llamado “proceso de primer plano”, mientras que todos los demás durante ese tiempo son “procesos de fondo”. El proceso de “primer plano” tiene libre acceso al tty. Por el contrario, los procesos “en segundo plano” serán interrumpidos por el kernel si se atreven a acceder a su tty. No es que los procesos en segundo plano no estén permitidos en absoluto, es más bien que el núcleo les indica que “no es su turno de hablar”.
Entonces, yendo a sus preguntas específicas:
¿Qué significan exactamente “primer plano” y “fondo”?
“Primer plano” significa “ser legítimamente usando el tty en ese momento “
“Fondo” significa simplemente “no estar usando el tty en ese momento”
O, en otras palabras, nuevamente citando sus preguntas:
Quiero saber qué diferencia a los procesos en primer plano y en segundo plano.
Legítimo acceso al tty.
¿Es posible traer un proceso en segundo plano al primer plano mientras el padre continúa ejecutándose?
En términos generales: procesos en segundo plano (padre o no) hacer continúan ejecutándose, es solo que se detienen (por defecto) si intentan acceder a su tty. (Nota: pueden ignorar o manejar esas señales específicas (SIGTTIN y SIGTTOU) pero ese no suele ser el caso, por lo tanto, la disposición predeterminada es suspender el proceso.)
Sin embargo: en el caso de un shell interactivo, es la cáscara que opta por suspenderse (en una espera (2) o seleccionar (2) o cualquier sistema de bloqueo de llamada que crea que es el más apropiado para ese momento) después de entregar el tty a uno de sus hijos que estaba en segundo plano .
A partir de esto, la respuesta precisa a esa pregunta específica suya es: cuando se usa una aplicación de shell depende de si el shell que estás usando te da un método (comando incorporado o qué) para no detenerse después de haber emitido el fg
mando. AFAIK Bash no te permite esa elección. No conozco otras aplicaciones de shell.
que hace
cmd &
diferente decmd
?
En un cmd
, Bash genera un nuevo proceso que pertenece a su propia sesión, le entrega el tty y se pone en espera.
En un cmd &
, Bash genera un nuevo proceso que pertenece a su propia sesión.
cómo entregar el control de primer plano a un proceso hijo
En términos generales: necesitas usar tcsetpgrp (3). En realidad, esto puede hacerlo un padre o un hijo, pero la práctica recomendada es que lo haga uno de los padres.
En el caso específico de Bash: se emite el fg
comando, y al hacerlo, Bash usa tcsetpgrp (3) a favor de ese niño y luego se pone a esperar.
A partir de aquí, otra perspectiva que podría resultarle de interés es que, en realidad, en sistemas UNIX bastante recientes, existe un nivel adicional de jerarquía entre los procesos de una sesión: el llamado “grupo de proceso”.
Esto está relacionado porque lo que he dicho hasta ahora con respecto al concepto de “primer plano” en realidad no se limita a “un solo proceso”, sino que se expandirá a “un solo grupo de procesos”.
Es decir: sucede que el habitual común caso de “primer plano” es de un solo proceso que tiene acceso legítimo al tty, pero el kernel realmente permite un caso más avanzado donde todo un grupo de procesos (que aún pertenecen a la misma sesión) tienen acceso legítimo al tty.
De hecho, no es un error que la función a llamar para entregar tty “primer plano” se llame tcsetpgrp, y no algo como (p. ej.) tcsetpid.
Sin embargo, en términos prácticos, es evidente que Bash no aprovecha esta posibilidad más avanzada, ya propósito.
usted Sin embargo, es posible que desee aprovecharlo. Todo depende de su aplicación específica.
Solo como un ejemplo práctico de agrupación de procesos, podría haber optado por utilizar un enfoque de “recuperar grupo de procesos en primer plano” en mi solución anterior, en lugar del enfoque de “grupo de transferencia en primer plano”.
Es decir, podría haber hecho que el script de Python use la función os.setpgid () (que envuelve la llamada al sistema setpgid (2)) para reasignar el proceso al grupo de procesos en primer plano actual (probablemente el proceso de shell en sí, pero no necesariamente), recuperando así el estado de primer plano que Bash no había entregado.
Sin embargo, esa sería una forma bastante indirecta hacia el objetivo final y también podría tener efectos secundarios indeseables debido al hecho de que hay varios otros usos de los grupos de procesos no relacionados con el control de tty que podrían terminar involucrando a su coproc en ese momento. Por ejemplo, las señales de UNIX en general se pueden enviar a un grupo de procesos completo, en lugar de a un solo proceso.
Finalmente, ¿por qué es tan diferente invocar su propio ejemplo? run()
funcionar desde el símbolo del sistema de Bash en lugar de desde un script (o como un texto) ?
Porque run()
invocado desde un símbolo del sistema es ejecutado por el propio proceso de Bash
mientras que cuando se invoca desde un script, se ejecuta mediante un proceso diferente (-group) al que el Bash interactivo ya ha entregado felizmente el tty.
Por lo tanto, a partir de un script, la última “defensa” final que Bash pone en su lugar para evitar competir con el tty es fácilmente eludida por el conocido truco de guardar y restaurar los descriptores de archivo stdin / stdout / stderr. o posiblemente genere un nuevo proceso perteneciente a
su propio mismo
grupo-proceso. En realidad, nunca investigué qué enfoque exacto usa un Bash interactivo para ejecutar funciones, pero no hace ninguna diferencia en términos de tty.
HTH