假设我有一个看起来像这样的bash脚本:
array=( 1 2 3 4 5 6 )
for each in "${array[@]}"
do
echo "$each"
command --arg1 $each
done
如果我想并行运行循环中的所有内容,我可以将command --arg1 $each
更改为command --arg1 $each &
。
但现在让我们说我想取command --arg1 $each
的结果并对这些结果做点什么:
array=( 1 2 3 4 5 6 )
for each in "${array[@]}"
do
echo "$each"
lags=($(command --arg1 $each)
lngth_lags=${#lags[*]}
for (( i=1; i<=$(( $lngth_lags -1 )); i++))
do
result=${lags[$i]}
echo -e "$timestamp\t$result" >> $log_file
echo "result piped"
done
done
如果我只是在&
的末尾添加command --arg1 $each
,那么command --arg1 $each
之后的所有内容都将在没有command --arg1 $each
完成的情况下运行。我该如何防止这种情况发生?另外,我如何限制循环可以占用的线程数量?
基本上,此块应与1,2,3,4,5,6
echo "$each"
lags=($(command --arg1 $each)
lngth_lags=${#lags[*]}
for (( i=1; i<=$(( $lngth_lags -1 )); i++))
do
result=${lags[$i]}
echo -e "$timestamp\t$result" >> $log_file
echo "result piped"
done
----- EDIT --------
以下是原始代码:
#!/bin/bash
export KAFKA_OPTS="-Djava.security.krb5.conf=/etc/krb5.conf -Djava.security.auth.login.config=/etc/kafka/kafka.client.jaas.conf"
IFS=$'\n'
array=($(kafka-consumer-groups --bootstrap-server kafka1:9092 --list --command-config /etc/kafka/client.properties --new-consumer))
lngth=${#array[*]}
echo "array length: " $lngth
timestamp=$(($(date +%s%N)/1000000))
log_time=`date +%Y-%m-%d:%H`
echo "log time: " $log_time
log_file="/home/ec2-user/laglogs/laglog.$log_time.log"
echo "log file: " $log_file
echo "timestamp: " $timestamp
get_lags () {
echo "$1"
lags=($(kafka-consumer-groups --bootstrap-server kafka1:9092 --describe --group $1 --command-config /etc/kafka/client.properties --new-consumer))
lngth_lags=${#lags[*]}
for (( i=1; i<=$(( $lngth_lags -1 )); i++))
do
result=${lags[$i]}
echo -e "$timestamp\t$result" >> $log_file
echo "result piped"
done
}
for each in "${array[@]}"
do
get_lags $each &
done
------编辑2 -----------
尝试以下答案:
#!/bin/bash
export KAFKA_OPTS="-Djava.security.krb5.conf=/etc/krb5.conf -Djava.security.auth.login.config=/etc/kafka/kafka.client.jaas.conf"
IFS=$'\n'
array=($(kafka-consumer-groups --bootstrap-server kafka1:9092 --list --command-config /etc/kafka/client.properties --new-consumer))
lngth=${#array[*]}
echo "array length: " $lngth
timestamp=$(($(date +%s%N)/1000000))
log_time=`date +%Y-%m-%d:%H`
echo "log time: " $log_time
log_file="/home/ec2-user/laglogs/laglog.$log_time.log"
echo "log file: " $log_file
echo "timestamp: " $timestamp
max_proc_count=8
run_for_each() {
local each=$1
echo "Processing: $each" >&2
IFS=$'\n' read -r -d '' -a lags < <(kafka-consumer-groups --bootstrap-server kafka1:9092 --describe --command-config /etc/kafka/client.properties --new-consumer --group "$each" && printf '\0')
for result in "${lags[@]}"; do
printf '%(%Y-%m-%dT%H:%M:%S)T\t%s\t%s\n' -1 "$each" "$result"
done >>"$log_file"
}
export -f run_for_each
export log_file # make log_file visible to subprocesses
printf '%s\0' "${array[@]}" |
xargs -P "$max_proc_count" -n 1 -0 bash -c 'run_for_each "$@"'
答案 0 :(得分:2)
方便的做法是将后台代码推送到单独的脚本或导出的函数中。这样xargs
可以创建一个新shell,并从其父级访问该函数。 (确保export
孩子也需要其他任何变量。
array=( 1 2 3 4 5 6 )
max_proc_count=8
log_file=out.txt
run_for_each() {
local each=$1
echo "Processing: $each" >&2
IFS=$' \t\n' read -r -d '' -a lags < <(yourcommand --arg1 "$each" && printf '\0')
for result in "${lags[@]}"; do
printf '%(%Y-%m-%dT%H:%M:%S)T\t%s\t%s\n' -1 "$each" "$result"
done >>"$log_file"
}
export -f run_for_each
export log_file # make log_file visible to subprocesses
printf '%s\0' "${array[@]}" |
xargs -P "$max_proc_count" -n 1 -0 bash -c 'run_for_each "$@"'
一些注意事项:
echo -e
是不好的形式。请参阅the POSIX spec for echo
中的“应用程序使用”和“RATIONALE”部分,明确建议使用printf
代替(不定义-e
选项,明确定义比echo
不得接受-n
以外的任何选项。each
值,以便稍后从中提取。yourcommand
的输出是以空格分隔,制表符分隔,行分隔还是其他方式。我现在接受所有这些;修改传递给IFS
的{{1}}的值来品尝。read
获取没有printf '%(...)T'
等外部工具的时间戳需要bash 4.2或更新版本。如果您认为合适,请替换为您自己的代码。date
比read -r -a arrayname < <(...)
强大得多。特别是,它避免将发出的值视为globs - 将arrayname=( $(...) )
替换为当前目录中的文件列表,或者*
替换为Foo[Bar]
如果存在该名称的任何文件(或,如果设置了FooB
或failglob
选项,则在这种情况下触发失败或根本不发出任何值。)nullglob
一次比每次运行log_file
一次重定向更有效。请注意,让多个进程同时写入同一个文件只有在所有进程都使用printf
(O_APPEND
将会执行)打开的情况下才会安全,并且如果它们以块的形式写入小到足以单独完成单个系统调用(除非单个>>
值非常大,否则可能正在发生)。答案 1 :(得分:2)
这里有许多长篇大论和理论上的答案,我会尽量保持简单 - 如何使用|
(管道)照常连接命令?;)(和GNU parallel
,擅长这些类型的任务)。
seq 6 | parallel -j4 "command --arg1 {} | command2 > results/{}"
-j4
将根据请求限制线程数(作业)。您不希望从多个作业写入单个文件,每个作业输出一个文件,并在并行处理完成后加入它们。
答案 2 :(得分:0)
您知道如何在单独的进程中执行命令。缺少的部分是如何允许这些进程进行通信,因为单独的进程无法共享变量。
基本上,您必须选择是使用常规文件进行通信,还是进程间通信/ FIFO(仍然归结为使用文件)。
一般方法:
决定如何呈现要执行的任务。您可以将它们作为文件系统上的单独文件,作为可以从中读取的FIFO特殊文件等。这可以是一个简单的操作,即将每个命令写入单独的文件,或将每个命令写入FIFO(一个每行命令)。
在主要流程中,准备描述要执行的任务的文件,或在后台启动一个单独的流程,以便为FIFO提供信息。
然后,仍然在主进程中,在后台启动工作进程(使用&
),就像您希望执行并行任务一样(不是每个任务要执行一个)。启动后,使用wait
,等待所有进程完成。单独的进程不能共享变量,您必须编写以后需要用来分隔文件的任何输出,或FIFO等。如果使用FIFO,请记住多个进程可以同时写入FIFO,所以使用某种互斥机制(我建议为此目的考虑使用mkdir
/ rmdir
)。
每个工作进程必须获取下一个任务(从文件/ FIFO),执行它,生成输出(到文件/ FIFO),循环直到没有新任务,然后退出。如果使用文件,则需要使用互斥锁“保留”文件,读取文件,然后将其删除以将其标记为已处理。 FIFO不需要这样做。
根据具体情况,您的主进程可能必须等到所有任务完成才能处理输出,或者在某些情况下可能会启动一个工作进程,该进程将检测并处理输出。一旦执行完所有任务,该工作进程必须由主进程停止,或者在所有任务执行完毕并退出时(在主进程被wait
编辑时)自行确定。 / p>
这不是详细的代码,但我希望它能让您了解如何处理这样的问题。
答案 3 :(得分:0)
(社区维基回答问题中的OP's proposed self-answer - 现已编辑完毕):
所以这是我可以想到这样做的一种方式,不确定这是否是最有效的方式,而且,我无法控制线程的数量(我认为或进程?),这将使用:
array=( 1 2 3 4 5 6 )
lag_func () {
echo "$1"
lags=($(command --arg1 $1)
lngth_lags=${#lags[*]}
for (( i=1; i<=$(( $lngth_lags -1 )); i++))
do
result=${lags[$i]}
echo -e "$timestamp\t$result" >> $log_file
echo "result piped"
done
}
for each in "${array[@]}"
do
lag_func $each &
done
答案 4 :(得分:0)
使用GNU Parallel看起来像这样:
array=( 1 2 3 4 5 6 )
parallel -0 --bar --tagstring '{= $_=localtime(time)."\t".$_; =}' \
command --arg1 {} ::: "${array[@]}" > output
GNU Parallel确保不会混合来自不同作业的输出。
如果您更喜欢混合作业的输出:
parallel -0 --bar --line-buffer --tagstring '{= $_=localtime(time)."\t".$_; =}' \
command --arg1 {} ::: "${array[@]}" > output-linebuffer
再次,GNU Parallel确保只与整行混合:你不会看到一个作业的半行和另一个作业的半行。
如果数组有点讨厌,它也有效:
array=( "new
line" 'quotes" '"'" 'echo `do not execute me`')
或者如果命令打印长行半行:
command() {
echo Input: "$@"
echo '" '"'"
sleep 1
echo -n 'Half a line '
sleep 1
echo other half
superlong_a=$(perl -e 'print "a"x1000000')
superlong_b=$(perl -e 'print "b"x1000000')
echo -n $superlong_a
sleep 1
echo $superlong_b
}
export -f command
GNU Parallel努力成为一般解决方案。这是因为我设计了GNU Parallel以关注正确性,并且在保持合理快速的同时,也要努力正确处理角落情况。
GNU Parallel防范竞争条件,并且不会在每行的输出中拆分单词。
array=( $(seq 30) )
max_proc_count=30
command() {
# If 'a', 'b' and 'c' mix: Very bad
perl -e 'print "a"x3000_000," "'
perl -e 'print "b"x3000_000," "'
perl -e 'print "c"x3000_000," "'
echo
}
export -f command
parallel -0 --bar --tagstring '{= $_=localtime(time)."\t".$_; =}' \
command --arg1 {} ::: "${array[@]}" > parallel.out
# 'abc' should always stay together
# and there should only be a single line per job
cat parallel.out | tr -s abc
如果输出有很多单词,GNU Parallel工作正常:
array=(1)
command() {
yes "`seq 1000`" | head -c 10M
}
export -f command
parallel -0 --bar --tagstring '{= $_=localtime(time)."\t".$_; =}' \
command --arg1 {} ::: "${array[@]}" > parallel.out
GNU Parallel不会占用你所有的内存 - 即使输出大于你的RAM:
array=(1)
outputsize=1000M
export outputsize
command() {
yes "`perl -e 'print \"c\"x30_000'`" | head -c $outputsize
}
export -f command
parallel -0 --bar --tagstring '{= $_=localtime(time)."\t".$_; =}' \
command --arg1 {} ::: "${array[@]}" > parallel.out