有没有办法可以在 Unix 终端中执行以下操作:
- 启动长时间运行的进程
- 添加另一个长时间运行的进程,在前一个进程完成后启动
- 重复步骤 2,直到我将需要运行的进程排入队列
我问这个问题的原因是我需要做一些长时间运行的事情。我可以将所有命令放在一个简单的 bash 脚本中并执行它,但问题是我并不总是确定我需要运行什么。所以如果我启动脚本然后想起另一个我应该运行的脚本,我需要多次按下 ^C 直到所有进程都被终止,编辑脚本并添加我的新进程,然后重新启动整个过程。
我现在具体要做的事情是将大量大文件复制到各种外部硬盘上,由于我不知道具体需要复制哪些文件以及复制到哪里,所以我想先从我知道需要复制的文件开始,然后在弄清楚其余文件后再将它们添加到队列中。
希望这是有意义的...这样的事情可能发生吗?
答案1
这个外壳非常适合这个用途。
- 启动第一个程序。
- 按下
Ctrl-z
可暂停程序。 - 开始下一个程序。
- 重复步骤 2 和 3,将更多程序添加到队列中。所有程序都将处于暂停模式。
现在像这样运行队列:
while fg; do :; done
在队列完成之前,您可能无法暂停此 while 循环或退出 bash。
如果您需要注销(例如程序将运行很多天),那么请考虑运行上述步骤screen
。
答案2
不是很优雅,但是又快又脏:
process1 &
while kill -0 $! ; do sleep 1; done && process2 &
while kill -0 $! ; do sleep 1; done && process3 &
$!
如果您运行了中间的后台作业,则需要替换实际的 PID 。
答案3
对你这个一般性问题的回答是:当然。在计算的早期,批处理是做任何事情的唯一方法,即使在发明了多用户交互系统之后,批处理能力也是大型作业的常态。如今,在中型和大型环境中,使用以下系统仍然很常见:太阳网格引擎或者扭矩。
但是,这可能超出了您的需要。您可以设置一个更轻量级的系统来在串行队列中运行脚本,但我认为这种方法并不特别适合您的特定任务。假设并行复制到不同的驱动器是可以接受的,我想我会像这样解决它:
创建与您的目标驱动器相对应的目录结构:
~/copysystem/drive1/
~/copysystem/drive2/
~/copysystem/drive3/
安装英科龙。
为每个目录设置一个 incrontab 条目,当出现 IN_MOVED_TO 时,该条目会自动运行复制脚本。
制作脚本任何一个a) 在启动时终止同一脚本的所有先前实例或 b) 使用
mkdir
基于的锁文件并阻止,直到锁被清除。
然后,您需要做的就是将文件移动到各个~/copysystem/drive#
目录,它们都会神奇地复制到您的目的地。
尤其是在 4a 的情况下,您可能希望使用 来rsync -aP
复制文件,以便可以从中间重新开始部分传输。(--remove-sent-files
如果您想删除原始文件,可以与 结合使用。)
如果您想跳过使用 incron 的复杂性,您仍然可以利用让脚本在锁定文件上阻塞的优势。 它的工作原理如下:
#!/bin/bash
LOCKDIR="/var/run/copysystem/copysystem.lock"
while ! mkdir $LOCKDIR ; do
echo "waiting for lock"
sleep 5
done
trap rmdir $LOCKDIR EXIT
rsync commands go here....
这样做是因为mkdir
它是一项原子操作 — 如果成功,您就知道目录不存在。这很重要,因为如果您使用类似的东西! -f && touch
,就会出现竞争条件。(与扫描进程表中的 rsync 命令或类似命令相同。)
答案4
您需要的是非交互式命令队列。好消息!我为您写了一个。
enqueue_cmd
:
#!/usr/bin/perl -w
# Enqueue a job
use strict;
use Fcntl qw(:flock);
my $JOB_QUEUE_FILE = '/var/tmp/job_queue';
my $LOCK_TRIES = 5;
my $LOCK_SLEEP = 1;
my $jq;
open $jq, ">> $JOB_QUEUE_FILE" or die "!open $JOB_QUEUE_FILE: $!";
my $locked = 0;
LOCK_ATTEMPT: for (my $lock_tries = 0; $lock_tries < $LOCK_TRIES;
$lock_tries++)
{
if (flock $jq, LOCK_EX) {
$locked = 1;
last LOCK_ATTEMPT;
}
sleep $LOCK_SLEEP;
}
$locked or die "could not lock $JOB_QUEUE_FILE";
for (@ARGV) {
print $jq "$_\n";
}
close $jq;
dequeue_cmd
:
#!/usr/bin/perl -w
# Dequeue a jobs and run them
use strict;
use Fcntl qw(:seek :flock);
use FileHandle;
my $QUEUE_FILE = '/var/tmp/job_queue';
my $OUTPUT_FILE = '/var/tmp/job_out';
my $LOCK_TRIES = 5;
my $LOCK_SLEEP = 1;
my $JOB_SLEEP = 1;
my $locked;
my $jo;
open $jo, ">> $OUTPUT_FILE" or die "!open $OUTPUT_FILE: $!";
$jo->autoflush(1);
my $jq;
open $jq, "+< $QUEUE_FILE" or die "!open $QUEUE_FILE: $!";
my @jobs = ( );
my $job;
JOB: while (1) {
if (-s $QUEUE_FILE == 0) {
sleep $JOB_SLEEP;
next JOB;
}
$locked = 0
LOCK_ATTEMPT: for (my $lock_tries = 0; $lock_tries < $LOCK_TRIES;
$lock_tries++)
{
if (flock $jq, LOCK_EX) {
$locked = 1;
last LOCK_ATTEMPT;
}
sleep $LOCK_SLEEP;
}
$locked or die "could not lock $QUEUE_FILE";
seek $jq, 0, SEEK_SET or die "could not seek to start of file";
push @jobs, <$jq>;
truncate $jq, 0 or die "could not truncate $QUEUE_FILE";
seek $jq, 0, SEEK_SET or die "could not seek to start of $QUEUE_FILE";
flock $jq, LOCK_UN;
for $job (@jobs) {
chomp $job;
print $jo "## executing $job\n";
print $jo `$job`;
}
sleep $JOB_SLEEP;
}
首先,运行nohup ./dequeue_cmd &
,然后添加命令,如下所示:
./enqueue_cmd "echo 'hello world'" "sleep 5" "date"
./enqueue_cmd "ls /var/tmp"
输出出现在/var/tmp/job_out
:
tail -F /var/tmp/job_out
## executing echo 'hello world'
hello world
## executing sleep 5
## executing date
Fri Dec 10 16:35:43 PST 2010
## executing ls /var/tmp
ff
job_out
job_queue
ss