脚本中的多个后台进程

mun*_*ish 1 linux process shell-script process-management

假设如果我遇到需要复制某些文件并且需要很长时间的情况,那么我将不得不并行处理文件副本。例如它可能看起来像这样

for i in ipaddresslist
do

cp x y &  //running in back ground or some other process in background

done

wait  //will cause till all the files are copied or whatever process if all are finished
Run Code Online (Sandbox Code Playgroud)

现在我已经使用,wait 以便完成所有背景过程,但其中有一些情况,例如

1)某些文件复制可能会更早发生,因此如果我必须对这些文件进行一些处理,我必须等到所有文件都被复制。

2)如果复制进程(或在后台运行的任何其他程序)写入日志文件,日志文件可能会因每个后台进程同时尝试写入文件而出现乱码。

是否有针对此类事情的解决方法,1)我的意思是,如果我知道这个过程已经完成,那么我可以开始为该过程的特定实例(比如文件复制)进行其余的处理(如果它完成)。此外,写入日志文件也可以有序进行。

请提出建议

Sté*_*las 5

如果在复制某些文件后需要启动某些作业,只需将其作为后台作业的一部分即可:

(cp this /there && start job that needs this in /there) &
(cp that /here && start job that needs that in /here) &
wait
Run Code Online (Sandbox Code Playgroud)

(最后一个&不是必需的)。

现在对于更复杂的依赖项,您可以使用 GNU make -j

make -j2 -f /dev/fd/3 3<< 'EOF'
all: j1 j2 j3
.PHONY: cp1 cp2 cp3 j1 j2 j3 all

cp1:
    cp this /there

cp2:
    cp that /here

cp3:
    cp this /here

j1: cp1
    start job that needs this in /there

j2: cp2
    start job that needs that in /here

j3: cp1 cp3
    start job that needs this in /here and /there
EOF
Run Code Online (Sandbox Code Playgroud)

-j2 在任何给定时间最多可以运行 2 个作业,并且会尊重依赖关系。

现在为了避免日志文件出现乱码,您有两个主要选择

  1. 不要交错它们,即一个接一个地附加每个作业的内容。
  2. 尽量确保它们很好地交错,可能会标记每个作业的每一行,以便更容易查看哪一行属于哪个作业。

对于 1,最简单的方法是将每个作业输出存储在一个单独的文件中,然后将它们合并:

(cp this /there && start job that needs this in /there) > j1.log 2>&1 &
(cp that /here && start job that needs that in /here) > j2.log 2>&1 &
wait
cat j1.log j2.log > jobs.log
Run Code Online (Sandbox Code Playgroud)

另一种选择是使用管道来收集每个作业的输出并cat合并它们。中可用的 Shell 进程替换kshzsh或者bash可以帮助我们解决这个问题,甚至可以处理背景:

j1() { cp this /there && start job that needs this in /there; }
j2() { cp that /here && start job that needs that in /here; }
cat <(j1 2>&1) <(j2 2>&1) > jobs.log
Run Code Online (Sandbox Code Playgroud)

j1j2并且cat将同时启动,并用管道连接间。

但是请注意,cat只有j2j1完成后才会从第二个管道(由 写入)开始读取。这意味着如果j2写入的日志记录超过管道的大小(例如,在 Linux 上,通常为 64kiB)j2将被阻塞,直到j1完成。

这可以通过使用spongefrom来避免moreutils,例如:

cat <(j1 2>&1) <(j2 2>&1 | sponge) > jobs.log
Run Code Online (Sandbox Code Playgroud)

虽然这将意味着所有的输出j2将被存储在内存中,猫只会开始写的输出j2jobs.logj2完成,在这种情况下使用pv -qB 100M,例如可优选:

cat <(j1 2>&1) <(j2 2>&1 | pv -qB 100M) > jobs.log
Run Code Online (Sandbox Code Playgroud)

这种方式j2只会在输出日志(加上两个管道内容j1)之后暂停(如果尚未完成)100M,并且pv不会j2在输出到标准输出之前等待完成。

请注意,对于上述所有内容,您需要注意,一旦将大多数命令的输出重定向到文件或管道(除了 tty 之外的任何内容),行为就会受到影响。命令,或者更确切地说,它们调用的stdioAPI libc( printf, fputs, fwrite...) 检测到输出不会到达终端并通过以大块(几千字节)输出来执行优化,而它们不这样做对于标准误差。这意味着输出和错误消息的顺序将受到影响。如果这是一个问题,在 GNU 系统或 FreeBSD(至少)以及动态链接的命令上,您可以使用以下stdbuf命令:

stdbuf -oL j1 > j1.log 2>&1
Run Code Online (Sandbox Code Playgroud)

代替:

j1 > j1.log 2>&1
Run Code Online (Sandbox Code Playgroud)

确保 stdio 输出是行缓冲的(每行输出将在完成后单独写入)。

对于选项 2,写入小于PIPE_BUF字节的管道,在 Linux 上比平均日志行大 4096 字节,保证是原子的,也就是说,如果两个进程同时写入同一个管道,它们的2 次写入保证不会交织在一起。常规文件没有这样的保证,但我严重怀疑 2 次小于 4kiB 的写入最终会在任何操作系统或文件系统上交织在一起。

因此,如果不是上述缓冲,并且如果日志行作为一个整体单独输出,则您可以保证输出行不会包含此作业的一行和一块其他工作的线。

但是,没有什么可以阻止命令在正在写入的行的两个部分之间进行刷新(例如printf("foo"); fflush(stdout); printf("bar\n");),并且 stderr 上没有缓冲。

另一个问题是,一旦所有作业的行都交错,就很难分辨哪一行用于哪个作业。

您可以通过执行以下操作来解决这两个问题:

tag() { stdbuf -oL sed "s%^%$1: %"; }
{
  j1 2>&1 | tag j1 &
  j2 2>&1 | tag j2
} | cat > jobs.log
Run Code Online (Sandbox Code Playgroud)

(请注意,我们不需要wait(并且在大多数 shell 中它无论如何都不会工作),因为cat在没有人再向管道写入数据之前不会完成,所以直到j1并且j2已经终止)。

上面我们使用| cat具有原子性保证的管道。我们将每个命令的输出通过管道传输到一个命令,该命令用作业名称标记每一行。j1并且j2可以随心所欲地编写他们的输出,sed(因为stdbuf -oL)会将行(带有标签前缀)作为一个整体单独输出,这将保证输出不会被破坏。

与上面相同的注意事项仍然适用:我们没有应用stdbuf -oL到命令中j1j2因此它们很可能会缓冲其输出,因此可能会在生成后很长时间写入。这甚至比前一种情况更糟,因为如果您看到:

j1: doing A
j1: doing B
j2: doing C
Run Code Online (Sandbox Code Playgroud)

这确实意味着j1在执行 B 之前执行了 A,但并不是在j2执行 C之前执行了其中任何一个。因此,stdbuf -oL如果有问题,您可能需要应用更多命令。

请注意,您不能应用于stdbuf类似j1j2以上的shell 函数,但至少对于 GNU 和 FreeBSD stdbuf,您可以使用它来stdbuf全局设置或在每个子shell 的基础上设置:

stdbuf_LD_PRELOAD=$(stdbuf sh -c 'export -p LD_PRELOAD')
line_buffered_output() {
  eval "$stdbuf_LD_PRELOAD"
  export _STDBUF_O=L
}
j1() (line_buffered_output; cp this /there && start...)
Run Code Online (Sandbox Code Playgroud)