web-dev-qa-db-fra.com

bash: lance plusieurs commandes chaînées en arrière plan

J'essaie d'exécuter des commandes en parallèle, en arrière-plan, en utilisant bash. Voici ce que j'essaie de faire:

forloop {
  //this part is actually written in Perl
  //call command sequence
  print `touch .file1.lock; cp bigfile1 /destination; rm .file1.lock;`;
}

La partie entre les backticks (``) génère un nouveau shell et exécute les commandes successivement. Le problème, c’est que le contrôle du programme original ne revient que lorsque la dernière commande a été exécutée. J'aimerais exécuter la totalité de l'instruction en arrière-plan (je n'attends aucune valeur de sortie/retour) et j'aimerais que la boucle continue de s'exécuter.

Le programme appelant (celui qui a la boucle) ne se terminera pas tant que tous les shells engendrés n’auront pas fini.

Je pourrais utiliser des threads en Perl pour faire apparaître des threads différents qui appellent des obus différents, mais cela semble excessif ...

Puis-je démarrer un shell, lui donner un ensemble de commandes et lui dire d'aller en arrière-plan?

43
Mad_Ady

Je n'ai pas testé cela mais qu'en est-il

print `(touch .file1.lock; cp bigfile1 /destination; rm .file1.lock;) &`;

Les parenthèses signifient exécuter dans une sous-coque mais cela ne devrait pas faire mal.

29
Hugh Allen

Merci Hugh, qui l'a fait:

adrianp@frost:~$ (echo "started"; sleep 15; echo "stopped")
started
stopped
adrianp@frost:~$ (echo "started"; sleep 15; echo "stopped") &
started
[1] 7101
adrianp@frost:~$ stopped

[1]+  Done                    ( echo "started"; sleep 15; echo "stopped" )
adrianp@frost:~$ 

Les autres idées ne fonctionnent pas car elles démarrent chaque commande en arrière-plan, et non la séquence de commande (ce qui est important dans mon cas!).

Merci encore!

19
Mad_Ady

Une autre façon consiste à utiliser la syntaxe suivante:

{ command1; command2; command3; } &
wait

Notez que le & se place à la fin du groupe de commandes, pas après chaque commande. Le point-virgule après la dernière commande est nécessaire, de même que l'espace après le premier crochet et avant le dernier crochet. La wait à la fin garantit que le processus parent n'est pas tué avant la fin du processus enfant généré (le groupe de commandes).

Vous pouvez également faire des choses fantaisistes comme rediriger stderr et stdout:

{ command1; command2; command3; } 2>&2 1>&1 &

Votre exemple ressemblerait à ceci:

forloop() {
    { touch .file1.lock; cp bigfile1 /destination; rm .file1.lock; } &
}
# ... do some other concurrent stuff
wait # wait for childs to end
18
lechup
for command in $commands
do
    "$command" &
done
wait

La perluète à la fin de la commande l'exécute en arrière-plan et la wait attend que la tâche en arrière-plan soit terminée.

14
GavinCattell

GavinCattell a eu le plus proche (pour bash, IMO), mais comme Mad_Ady l'a fait remarquer, il ne gérerait pas les fichiers "verrouillés". Ceci devrait: 

S'il y a d'autres tâches en attente, wait les attendra également. Si vous devez attendre uniquement _ les copies, vous pouvez accumuler ces PID et n'attendre que ceux-ci. Sinon, vous pouvez supprimer les 3 lignes avec "pids" mais c'est plus général.

De plus, j'ai ajouté des vérifications pour éviter la copie:

pids=
for file in bigfile*
do
    # Skip if file is not newer...
    targ=/destination/$(basename "${file}")
    [ "$targ" -nt "$file" ] && continue

    # Use a lock file:  ".fileN.lock" for each "bigfileN"
    lock=".${file##*/big}.lock"
    ( touch $lock; cp "$file" "$targ"; rm $lock ) &
    pids="$pids $!"
done
wait $pids

Incidemment, on dirait que vous copiez de nouveaux fichiers dans un référentiel FTP (ou similaire). Si tel est le cas, vous pourriez envisager une stratégie de copie/renommage au lieu des fichiers de verrouillage (mais c'est un autre sujet).

5
NVRAM

Le service que vous recherchez dans bash s'appelle Compound Commands. Voir la page de manuel pour plus d'informations:

Commandes composées Une commande composée est l’une des suivantes:

   (list) list  is  executed  in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below).  Variable assignments and
          builtin commands that affect the Shell's environment do not remain in effect after  the  command  completes.   The
          return status is the exit status of list.

   { list; }
          list  is  simply  executed in the current Shell environment.  list must be terminated with a newline or semicolon.
          This is known as a group command.  The return status is the exit status of list.  Note that unlike the metacharac‐
          ters  (  and  ),  {  and  } are reserved words and must occur where a reserved Word is permitted to be recognized.
          Since they do not cause a Word break, they must be separated from list by whitespace or another Shell  metacharac‐
          ter.

Il y en a d'autres, mais ce sont probablement les 2 types les plus courants. Le premier, les parens, exécutera une liste de commandes en série dans un sous-shell, tandis que le second, les accolades, affichera une liste de commandes en série dans le Shell actuel.

parens

% ( date; sleep 5; date; )
Sat Jan 26 06:52:46 EST 2013
Sat Jan 26 06:52:51 EST 2013

accolades

% { date; sleep 5; date; }
Sat Jan 26 06:52:13 EST 2013
Sat Jan 26 06:52:18 EST 2013
3
slm

Je suis tombé sur ce fil ici et j'ai décidé de créer un extrait de code pour générer des déclarations chaînées en tant que tâches d'arrière-plan. J'ai testé cela sur BASH pour Linux, KSH pour IBM AIX et Busybox's ASH pour Android, je pense donc qu'il est prudent de dire que cela fonctionne sur tout Bourne-like Shell.

processes=0;
for X in `seq 0 10`; do
   let processes+=1;
   { { echo Job $processes; sleep 3; echo End of job $processes; } & };
   if [[ $processes -eq 5 ]]; then
      wait;
      processes=0;
   fi;
done;

Ce code exécute un certain nombre de tâches en arrière-plan, dans la limite d'un certain nombre de tâches simultanées. Vous pouvez utiliser ceci, par exemple, pour recompresser un grand nombre de fichiers gzippés avec xz sans avoir un grand nombre de processus xz dévorer toute votre mémoire et faire en sorte que votre ordinateur se lève: dans ce cas, vous utilisez * comme liste for et le lot le travail serait gzip -cd "$X" | xz -9c > "${X%.gz}.xz".

2
RAKK

Exécutez la commande en utilisant un at job:

# date
# jue sep 13 12:43:21 CEST 2012
# at 12:45
warning: commands will be executed using /bin/sh
at> command1
at> command2
at> ...
at> CTRL-d
at> <EOT>
job 20 at Thu Sep 13 12:45:00 2012

Le résultat sera envoyé à votre compte par courrier.

2
Eliseo Carrasco

lancez les commandes dans un sous-shell:

(command1 ; command2 ; command3) &
2
Radu Simionescu

Essayez de mettre les commandes entre accolades avec & s, comme ceci:

{command1 & ; command2 & ; command3 & ; }

Cela ne crée pas de sous-shell, mais exécute le groupe de commandes en arrière-plan. 

HTH

1
Zsolt Botykai

Je ne sais pas pourquoi personne n'a répondu avec la solution appropriée:

my @children;
for (...) {
    ...
    my $child = fork;
    exec "touch .file1.lock; cp bigfile1 /destination; rm .file1.lock;" if $child == 0;
    Push @children, $child;
}
# and if you want to wait for them to finish,
waitpid($_) for @children;

Cela fait en sorte que Perl génère des enfants pour exécuter chaque commande et vous permet d'attendre que tous les enfants soient terminés avant de continuer.

Au fait,

print `some command`

et

system "some command"

affiche le même contenu sur stdout, mais le premier a une surcharge plus importante, car Perl doit capturer toutes les sorties de "some command"

1
ephemient

Juste au cas où quelqu'un est toujours intéressé, vous pouvez le faire sans appeler un sous-shell comme ceci:

print `touch .file1.lock && cp bigfile1 /destination && rm .file1.lock &`;
0

Vous pouvez utiliser la commande GNU parallel pour exécuter des travaux en parallèle. C'est plus sûr, plus vite. 

Je suppose que vous essayez de copier plusieurs gros fichiers de la source à la destination. Et pour cela, vous pouvez le faire en parallèle avec la déclaration ci-dessous.

$ ls *|parallel -kj0 --eta 'cp {} /tmp/destination'

Comme nous avons utilisé l'option -j0, tous les fichiers seront copiés en parallèle. Si vous devez réduire le nombre de processus parallèles, vous pouvez utiliser -j<n>, où <n> est le nombre de processus parallèles à exécuter.

Parallèle collectera également la sortie du processus et le rapportera de manière séquentielle (avec l'option -k), ce que ne peut pas faire l'autre mécanisme de contrôle de travail.

L'option --eta vous donnera des statistiques détaillées sur le processus en cours. Ainsi, nous pouvons savoir si le processus est terminé et combien de temps faudra-t-il pour le terminer.

0
Kannan Mohan