Cómo ejecutar comandos como en una cola

42

Necesito hacer muchas copias de varios archivos en varias carpetas. Puedo agregar todos mis comandos de copia a un script bash y luego ejecutar eso, pero luego debo esperar hasta que termine si quiero agregar más comandos a esa "cola" de copia.

¿Hay alguna forma de ejecutar comandos como una cola y agregar más comandos a esa cola mientras las cosas se están ejecutando?

Explicado de una manera diferente, quiero comenzar una tarea de larga duración. Mientras se está ejecutando, quiero iniciar otro que no se inicie hasta que se complete el primero. Y luego agregue otro después del último y así sucesivamente. ¿Es esto posible de alguna manera?

Svish
fuente
3
¿Puedo sugerirle que acepte una respuesta que resuelva su problema? Parece que hay un par de ellos que funcionarían para ti.
holmb
2
Sí, puedes, y yo he querido. El problema es que pregunté esto cuando trabajaba en una empresa que usaba Mac. Ya no estoy trabajando allí, ni tengo ningún Mac, así que no tengo forma de probar ninguno de estos. No me siento cómodo marcando una como respuesta cuando no puedo probar que realmente funciona, así que por ahora he esperado que las personas voten por las respuestas que encuentran que funcionan bien para que la "respuesta" pueda surgir de esa manera en lugar.
Svish

Respuestas:

25

La atutilidad, mejor conocida por ejecutar comandos en un momento específico, también tiene una función de cola y se le puede pedir que comience a ejecutar comandos ahora. Lee el comando para ejecutar desde la entrada estándar.

echo 'command1 --option arg1 arg2' | at -q myqueue now
echo 'command2 ...' | at -q myqueue now

El batchcomando es equivalente a at -q b -m now(lo que -msignifica que la salida del comando, si la hay, se le enviará por correo, como lo hace cron). No todas las variantes de Unix admiten nombres de cola ( -q myqueue); puede estar limitado a una sola cola llamada b. Linux atestá limitado a nombres de cola de una letra.

Gilles 'SO- deja de ser malvado'
fuente
2
En Linux, al menos, esto no parece esperar a que termine el comando anterior.
wds
@wds Funciona para mí en Debian wheezy. ¿Dónde y cómo se rompió para ti? Tenga en cuenta que solo espera a que termine el comando; si el comando inicia subprocesos que sobreviven a sus padres, at no espera los subprocesos.
Gilles 'SO- deja de ser malvado'
Probé con CentOS 6. Probé con solo un simple "sleep x". No estoy seguro de qué hace para ejecutar eso, pero supongo que inicia una subshell y esa subshell debería esperar el resultado (una llamada de suspensión no regresa de inmediato).
wds
Además de los nombres de la cola, 'now' tampoco parece estándar: bash en ubuntu se queja de eso ...
winwaed
@winwaed Oh, es now, no -t now, lo siento. No me había dado cuenta de que estaba equivocado en el código de muestra.
Gilles 'SO- deja de ser malvado'
23

Agregue &al final de su comando para enviarlo al fondo, y luego waiten él antes de ejecutar el siguiente. Por ejemplo:

$ command1 &
$ wait; command2 &
$ wait; command3 &
$ ...
Vortico
fuente
2
¡Hurra! esta sintaxis es increíble cuando has comenzado algo y luego vas a ver el stackoverflow para encontrar una forma de poner algo en cola después de él
Erik Aronesty
2
Si mientras tanto cambia de opinión sobre cualquiera de esos comandos, ¿qué se puede hacer para abortar wait? Parece impermeable a todos mis asesinatos.
Ken Williams
1
Solo matas el comando. El waitsimplemente se detiene el trabajo hasta que terminen las anteriores.
Gert Sønderby
¿Funcionará esto con nohup?
Michael
11

Las soluciones de Brad y Mankoff son buenas sugerencias. Otro que es similar a una combinación de ambos les sería utilizar GNU Screen para implementar su cola. Esto tiene la ventaja de poder ejecutarse en segundo plano, puede verificarlo cuando lo desee y poner en cola nuevos comandos simplemente los pega en el búfer para que se ejecuten después de que salgan los comandos anteriores.

Primer intento:

$ screen -d -m -S queue

(por cierto, ahora es un buen momento para jugar con algunos archivos impresionantes. screenrc )

Eso generará una sesión de pantalla de fondo para usted llamada cola.

Ahora, ponga en cola tantos comandos como desee:

screen -S queue -X stuff "echo first; sleep 4; echo second^M"

Estoy haciendo múltiples comandos en lo anterior solo para probar. Su caso de uso probablemente se parecería más a:

screen -S queue -X stuff "echo first^M"
screen -S queue -X stuff "echo second^M"

Tenga en cuenta que la "^ M" en mi línea anterior es una forma de obtener una nueva línea incrustada que se interpretará más tarde después de que la pantalla la inserte en su shell de bash existente. Use "CTL-V" para obtener esa secuencia.

Sería bastante fácil crear algunos scripts de shell simples para automatizar eso y poner en cola los comandos. Luego, cada vez que desee verificar el estado de su cola en segundo plano, se vuelve a adjuntar a través de:

screen -S queue -r

Técnicamente, ni siquiera necesita nombrar su sesión de pantalla y funcionará bien, pero una vez que se enganche, querrá dejar una en ejecución todo el tiempo de todos modos. ;-)

Por supuesto, si haces eso, otra buena manera de hacerlo sería nombrar una de las ventanas actuales "cola" y usar:

screen -S queue -p queue -X stuff "command"
Jordán
fuente
Parece que esto básicamente "teclea" el comando en la sesión de la pantalla en ejecución, de modo que cuando el shell vuelva a tomar el control después de que termine el primer comando, se iniciará este comando. En otras palabras, es equivalente a la solución de @mankoff, pero utiliza una herramienta más sofisticada para escribir.
Ken Williams
Prácticamente, la principal diferencia es que la solución de pantalla admite una mejor automatización. Puede hacer algunas cosas a mano, otras cosas con scripts, todo con una interfaz simple.
Jordan
@ Jordan Muy bien, me funciona. Pero, ¿qué es "cosas" para después de -X?
Konstantin
@Konstantin gnu.org/software/screen/manual/html_node/Paste.html#Paste (TL; DR: ingrese lo que sigue en el terminal como si lo hubiera escrito)
Jordan
@ Jordan Oh, ya veo ahora. Es un comando. Pensé que es una cadena arbitraria.
Konstantin
8

Hay una utilidad que he utilizado con gran éxito exactamente para el caso de uso que está describiendo. Recientemente trasladé mi computadora portátil principal a un nuevo hardware, lo que implicó mover algunos archivos a un NAS y el resto a la nueva máquina.

Así es como lo hice.

  1. Configure todas las máquinas involucradas con una conexión de red para que puedan comunicarse entre sí.

  2. En la máquina desde la que está moviendo los archivos (en adelante, la máquina fuente), instale rsync con apt-get install rsyncy la Tarea Spooler de secret-sauce (sitio web http://vicerveza.homeunix.net/~viric/soft/ts/ ). Si está en Debian, el nombre del paquete tses is task-spoolery se cambia el tspnombre del tsejecutable para evitar el choque de nombres con el ejecutable del moreutilspaquete. El paquete Debian vinculado desde el sitio web es perfectamente instalable en Ubuntu con dpkg -i task-spooler_0.7.3-1_amd64.debo similar.

  3. También asegúrese de que todas las máquinas tengan SSH instalado apt-get install openssh-server. En la máquina de origen, debe configurar SSH para permitir el inicio de sesión sin contraseña en las máquinas de destino. El método que la mayoría usará es la autenticación de clave pública con ssh-agent(consulte https://www.google.se/search?q=ssh+public+key+authentication para ver ejemplos de eso), pero a veces uso un método más fácil que funciona solo también con autenticación de contraseña. Agregue lo siguiente a la configuración de su cliente SSH (ya sea ~/.ssh/configo /etc/ssh/ssh_config):

    Host *
         ControlMaster auto
         ControlPath ~/.ssh/master-%r@%h:%p
    

    Luego abra una terminal en la máquina fuente, inicie sesión ssh target1, autentíquese como de costumbre y luego deje esta terminal abierta. Observe que hay un archivo de socket llamado ~/.ssh/master-user@target1:22, este es el archivo que mantendrá abierta una sesión maestra autenticada y permitirá conexiones sin contraseña posteriores user(siempre que la conexión use el mismo nombre de host y puerto de destino).

    En este punto, debe verificar que puede iniciar sesión sin que se le solicite la autenticación en las máquinas de destino.

  4. Ahora ejecute ts rsync -ave ssh bigfile user@target1:para un solo archivo o ts rsync -ave ssh bigdir user@target1:para un directorio. Con rsync es importante no incluir una barra inclinada final en el directorio (en bigdircomparación con bigdir/) o rsync supondrá que se refería al equivalente de la bigdir/*mayoría de las otras herramientas.

    Task Spooler devolverá el mensaje y le permitirá poner en cola muchos de estos comandos en sucesión. Inspeccione la cola de ejecución tssin argumentos.

Task Spooler tiene muchas características, como reorganizar la cola de ejecución, ejecutar un trabajo específico solo si otro trabajo se ejecutó correctamente, etc. Consulte la ayuda con ts -h. A veces inspecciono la salida del comando mientras se ejecuta con ts -c.

Existen otros métodos para hacer esto, pero para su caso de uso, todos incluyen Task Spooler. Elijo usar rsync a través de SSH para preservar las marcas de tiempo del archivo, que la copia a través de SMB no tendría.

hollow
fuente
gracias por su respuesta que no conocía ts, parece muy potente y fácil de usar, solo bifurcado en github autotoolizado y debianizado.
Alex
@Alex, eché un vistazo a tu bifurcación y me interesó especialmente lo que se hizo con respecto al autoenfriamiento y la debianización de la fuente original, y al ver tus confirmaciones no estaba disponible como una diferencia de la fuente original. ¿Le importaría forzar nuevos compromisos que en el paso 1. importan la fuente original y 2. se comprometen con sus adiciones? Ayudará a alguien que navega por su fuente a confiar (más fácilmente) en sus adiciones a la fuente original. Este es uno de los pocos paquetes que no instalo desde un PPA o similar, por lo que sería bueno construirlo yo mismo con su fork.
holmb
sabes que tengo que cambiar mis lentes, perdí la parte de tu respuesta sobre el existente task-spooler:-) ... de todos modos, eso es en tu comentario es una muy buena sugerencia, lo haré muy pronto.
Alex
hecho, eliminado y recreado el repositorio con commits progresivos, solo un par de errores (debería metabolizar más commits locales antes del push)
Alex
4

Necesito cosas como esta con bastante frecuencia también. Escribí una pequeña utilidad llamada afterque ejecuta un comando cada vez que algún otro proceso ha finalizado. Se parece a esto:

#!/usr/bin/perl

my $pid = shift;
die "Usage: $0 <pid> <command...>" unless $pid =~ /^\d+$/ && @ARGV;

print STDERR "Queueing process $$ after process $pid\n";
sleep 1 while -e "/proc/$pid";
exec @ARGV;

Luego lo ejecutas así:

% command1 arg1 arg2 ...  # creates pid=2853
% after 2853 command2 arg1 arg2 ... # creates pid=9564
% after 9564 command3 arg1 arg2 ...

La gran ventaja de esto sobre algunos otros enfoques es que el primer trabajo no necesita ejecutarse de ninguna manera especial, puede seguir cualquier proceso con su nuevo trabajo.

Ken Williams
fuente
Buen script @ken. Aunque recomendaría probar Task Spooler, ya que parece que lo necesita de vez en cuando. Mira mi respuesta.
holmb
Se ve bien, gracias. Lo único que falta en TS parece ser la capacidad de seguir trabajos que no se iniciaron bajo TS. Aunque supongo que podría comenzar un nuevo trabajo de TS cuyo propósito es esperar a que termine un proceso existente.
Ken Williams
En efecto. Ese es un aspecto útil de su solución.
holmb
2

Command1 && Command2

  • "&&" significa que el comando2 solo se ejecuta después de que el comando1 termina con un código de retorno de 0
  • Nota: a || significa que command2 solo se ejecuta después de que command1 finaliza con un código de retorno que no sea 0
naftuli
fuente
1

Simplemente puede agregar comandos a la línea de comandos del shell que ya está ejecutando un comando.

Escriba con cuidado o escríbalo en otro lugar, verifique y corte y pegue. Incluso si el comando actual arroja líneas de salida, puede escribir algo nuevo, presionar enter y se ejecutará cuando todos los comandos se ejecuten o ingresen antes de que se complete.

El siguiente ejemplo. Puede cortar y pegar todo, o ingresar los comandos posteriores mientras se ejecuta el primero (si escribe realmente muy rápido), o más probablemente mientras el segundo está en ejecución .:

echo "foo"
sleep 10; echo "bar"
sleep 3
echo "baz"

fuente
0

La forma más complicada que se me ocurre es mantener el "estado" de la cola con simples archivos de bloqueo en / tmp. cuando file1.sh está haciendo sus comandos cp, mantendrá un archivo de bloqueo (o enlace duro) en / tmp. cuando termine, borre el archivo. cualquier otro script tendrá que buscar en / tmp los archivos de bloqueo con el esquema de nombres que elija. naturalmente, esto está sujeto a todo tipo de fallas, pero es trivial de configurar y se puede hacer completamente dentro de bash.

Brad Clawsie
fuente
Difícil de usar en la práctica, ya que requiere que cada trabajo que desee ejecutar deba modificarse para mantener los archivos bloqueados, y necesite saber (o aceptar como parámetros) qué archivos bloqueados usar. Siempre que sea posible, es mejor tener la cola externa al trabajo.
Ken Williams