#!/usr/bin/perl # prun - parallel run processes # # This software is Public Domain. Please maintain version history. # Report all bugs to Greg Ercolano (erco@3dsite.com). # # 1.00 02/??/98 erco@3dsite.com perl4 version # x.xx xx/xx/xx - - # local($G_verbose) = 1; # verbose flag local($G_tmpdir) = "/var/tmp"; # tmp output files local(@G_run); # array of commands to run loaded from stdin sub HelpAndExit { print STDERR <<"EOF"; prun - parallel-run commands on stdin USAGE prun [options] < cmds OPTIONS -M # - sets maximum # forks at a given time (default=$maxcount) -q - quiet (disables RUN/DONE messages) EXAMPLE % cat file rdist -c file sun1:/usr/local/bin/file rdist -c file sun2:/usr/local/bin/file rdist -c file sun3:/usr/local/bin/file rdist -c file sun4:/usr/local/bin/file % prun < file DESCRIPTION Forks off all commands on stdin in parallel. Warning: garbage in = disaster out. Be careful. Blank lines and lines that start with # are ignored. The -M flag controls the maximum number of parallel processes that will be running at any given time. All commands have stdin redirected from /dev/null, and stdout/stderr redirected to a log file that is printed to the screen when each process finishes. Hitting the interrupt key will remove any leftover log files, and sends SIGKILL to all children still running. EOF exit(1); } # POST-INTERRUPT CLEANUP sub Cleanup { local ($pid); local ($tmp); # REMOVE TMP FILES FOR ANY RUNNING CHILDREN print "(Cleanup..)\n"; foreach $pid ( keys ( %children ) ) { $tmp = "$G_tmpdir/prun.$pid"; # KILL RUNNING CHILDREN if ( $G_verbose ) { print "CLEANUP: killing $pid\n"; } kill('INT', $pid); # REMOVE TMPFILE if ( ! -e $tmp ) { next; } if ( $G_verbose ) { print "CLEANUP: Removing $tmp..\n"; } unless ( unlink($tmp) ) { print STDERR "prun: ERROR: unlink($tmp): $!\n"; } } exit(1); } # ANNOUNCE START OR COMPLETION sub Started { local ($i, $count, $pid) = @_; if ( $G_verbose ) { print "RUN " . sprintf("%5d",$pid) . ", $count busy, " . ($#G_run - $i) . " to go: $children{$pid}\n"; } } # A PROCESS FINISHED sub Finish { local($i, $count, $pid) = @_; local($tmp) = "$G_tmpdir/prun.$pid"; # A CHILD FINISHED if ( $G_verbose ) { print "DONE " . sprintf("%5d",$pid) . ", $count busy, " . ($#G_run - $i) . " to go: $children{$pid}\n"; } # PRINT AND REMOVE OUTPUT FILE if ( -e $tmp ) { print "--- $children{$pid}\n"; open(OUT, "<$tmp"); while ( ) { print "$_"; } close(OUT); unless ( unlink($tmp) ) { print STDERR "prun: ERROR: unlink($tmp): $!\n"; } } delete($children{$pid}); } # MAIN { local ($i); local ($pid); local ($cmd); local (%children); # hash of children mapped by pid local ($count) = 0; local ($maxcount) = 4; # PARSE ARGS for ( $i=0; $i<=$#ARGV; $i++ ) { if ( $ARGV[$i] =~ /^-h/ ) { &HelpAndExit(); } elsif ( $ARGV[$i] =~ /^-q/ ) { $G_verbose = 0; } elsif ( $ARGV[$i] =~ /^-M/ ) { $i++; $maxcount = $ARGV[$i]; } else { print STDERR "prun: '$ARGV[$i]' unknown flag\n"; exit(1); } } # LOAD ALL COMMANDS FROM STDIN while ( ) { $cmd = $_; chop($cmd); # SKIP COMMENTS AND BLANK LINES if ( $cmd =~ /^#/ || $cmd =~ /^[\s]*$/ ) { next; } push(@G_run, $cmd); } if ( $G_verbose ) { print "$#G_run commands, batching $maxcount at a time\n"; } # SETUP AN INTERRUPT TRAP $SIG{'INT'} = 'Cleanup'; for ( $i=0; $i<=$#G_run; $i++ ) { $cmd = $G_run[$i]; # HIT MAXIMUM? WAIT FOR A CHILD TO FINISH if ( $count >= $maxcount ) { # WAIT FOR A CHILD if ( ($pid = wait()) != -1 ) { $count--; &Finish($i, $count, $pid); } } # START A NEW CHILD if ( ( $pid = fork()) == 0 ) { # CHILD local ($tmp) = "$G_tmpdir/prun.$$"; exec("$cmd > $tmp 2>&1 < /dev/null"); print STDERR "exec($cmd): $!\n"; exit(1); } else { # PARENT $children{$pid} = $cmd; &Started($i, $count, $pid); $count++; } } # NO MORE COMMANDS TO RUN? WAIT FOR CHILDREN while ( ( $pid = wait()) != -1) { $count--; &Finish($#G_run, $count, $pid); } exit(0); }