bash:使用文件列表限制for循环中的子shell

时间:2014-12-19 11:30:44

标签: bash for-loop subshell

我一直试图让for循环同时运行一堆命令,并试图通过子shell进行。我设法凑齐下面的脚本进行测试,似乎工作正常。

#!/bin/bash
for i in {1..255}; do
  (
    #commands
  )&

done
wait

唯一的问题是我的实际循环将是for i in files *然后它只是崩溃,我假设因为它启动了太多的子shell来处理。所以我添加了

#!/bin/bash
for i in files*; do
  (
    #commands
  )&
if (( $i % 10 == 0 )); then wait; fi
done
wait

现在失败了。有没有人知道这方面的方法?要么使用不同的命令来限制子壳的数量,要么为$ i提供一个数字?

干杯

4 个答案:

答案 0 :(得分:5)

xargs的/并行

另一种解决方案是使用专为并发而设计的工具:

printf '%s\0' files* | xargs -0 -P6 -n1 yourScript

-P6xargs将启动的最大并发进程数。如果你愿意,可以打10。

我建议xargs,因为它可能已经存在于您的系统中。如果您想要一个非常强大的解决方案,请查看GNU Parallel

数组中的文件名

对于您的问题明确的另一个答案:将计数器作为数组索引?

files=( files* )
for i in "${!files[@]}"; do
    commands "${files[i]}" &
    (( i % 10 )) || wait
done

(复合命令周围的括号并不重要,因为后台作业与使用子shell的效果相同。)

功能

只是不同的语义:

simultaneous() {
    while [[ $1 ]]; do
        for i in {1..11}; do
            [[ ${@:i:1} ]] || break
            commands "${@:i:1}" &
        done
        shift 10 || shift "$#"
        wait
    done
}
simultaneous files*

答案 1 :(得分:4)

您可以发现使用jobs来计算作业数量非常有用。 e.g:

wc -w <<<$(jobs -p)

所以,你的代码看起来像这样:

#!/bin/bash
for i in files*; do
  (
    #commands
  )&
  if (( $(wc -w <<<$(jobs -p)) % 10 == 0 )); then wait; fi
done
wait

正如@chepner建议:

在bash 4.3中,您可以在任何作业完成后立即使用wait -n,而不是等待所有

答案 2 :(得分:3)

明确定义计数器

#!/bin/bash
for f in files*; do
  (
    #commands
  )&
  (( i++ % 10 == 0 )) && wait
done
wait

无需初始化i,因为它会在您第一次使用时默认为0。还没有必要重置该值,因为对于i = 10,20,30等,i %10将为0

答案 3 :(得分:2)

如果您的Bash≥4.3,则可以使用wait -n

#!/bin/bash

max_nb_jobs=10

for i in file*; do
    # Wait until there are less than max_nb_jobs jobs running
    while mapfile -t < <(jobs -pr) && ((${#MAPFILE[@]}>=max_nb_jobs)); do
        wait -n
    done
    {
        # Your commands here: no useless subshells! use grouping instead
    } &
done
wait

如果您没有wait -n,则可以使用以下内容:

#!/bin/bash

set -m

max_nb_jobs=10

sleep_jobs() {
   # This function sleeps until there are less than $1 jobs running
   local n=$1
   while mapfile -t < <(jobs -pr) && ((${#MAPFILE[@]}>=n)); do
      coproc read
      trap "echo >&${COPROC[1]}; trap '' SIGCHLD" SIGCHLD
      [[ $COPROC_PID ]] && wait $COPROC_PID
   done
}

for i in files*; do
    # Wait until there are less than 10 jobs running
    sleep_jobs "$max_nb_jobs"
    {
        # Your commands here: no useless subshells! use grouping instead
    } &
done
wait

这样做的好处是,我们不会对完成工作所花费的时间做出任何假设。只要有足够的空间,就会启动一项新工作。而且,它都是纯粹的Bash,所以不依赖于外部工具和(可能更重要的是),你可以使用你的Bash环境(变量,函数等)而不导出它们(数组可以&#39 ; t很容易导出,这可能是一个巨大的专业人士。)