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)我的意思是,如果我知道这个过程已经完成,那么我可以开始为该过程的特定实例(比如文件复制)进行其余的处理(如果它完成)。此外,写入日志文件也可以有序进行。
请提出建议
如果在复制某些文件后需要启动某些作业,只需将其作为后台作业的一部分即可:
(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,最简单的方法是将每个作业输出存储在一个单独的文件中,然后将它们合并:
(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 进程替换ksh
,zsh
或者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)
j1
,j2
并且cat
将同时启动,并用管道连接间。
但是请注意,cat
只有j2
在j1
完成后才会从第二个管道(由 写入)开始读取。这意味着如果j2
写入的日志记录超过管道的大小(例如,在 Linux 上,通常为 64kiB)j2
将被阻塞,直到j1
完成。
这可以通过使用sponge
from来避免moreutils
,例如:
cat <(j1 2>&1) <(j2 2>&1 | sponge) > jobs.log
Run Code Online (Sandbox Code Playgroud)
虽然这将意味着所有的输出j2
将被存储在内存中,猫只会开始写的输出j2
在jobs.log
后j2
完成,在这种情况下使用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 之外的任何内容),行为就会受到影响。命令,或者更确切地说,它们调用的stdio
API 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
到命令中j1
,j2
因此它们很可能会缓冲其输出,因此可能会在生成后很长时间写入。这甚至比前一种情况更糟,因为如果您看到:
j1: doing A
j1: doing B
j2: doing C
Run Code Online (Sandbox Code Playgroud)
这确实意味着j1
在执行 B 之前执行了 A,但并不是在j2
执行 C之前执行了其中任何一个。因此,stdbuf -oL
如果有问题,您可能需要应用更多命令。
请注意,您不能应用于stdbuf
类似j1
或j2
以上的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)