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

bash:使用文件列表限制for循环中的子shell,bash,for-loop,subshell,Bash,For Loop,Subshell,我一直在尝试让for循环同时运行一系列命令,并试图通过子shell来实现。我设法拼凑了下面的脚本进行测试,它似乎工作正常 #!/bin/bash for i in {1..255}; do ( #commands )& done wait 唯一的问题是,我的实际循环将是文件中的i,然后它崩溃了,我想这是因为它启动了太多的子shell,无法处理。所以我补充说 #!/bin/bash for i in files*; do ( #commands )&am

我一直在尝试让for循环同时运行一系列命令,并试图通过子shell来实现。我设法拼凑了下面的脚本进行测试,它似乎工作正常

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

done
wait
唯一的问题是,我的实际循环将是文件中的i,然后它崩溃了,我想这是因为它启动了太多的子shell,无法处理。所以我补充说

#!/bin/bash
for i in files*; do
  (
    #commands
  )&
if (( $i % 10 == 0 )); then wait; fi
done
wait
现在失败了。有人知道怎么解决这个问题吗?或者使用不同的命令来限制子shell的数量,或者为$i提供一个数字


干杯

您会发现使用
作业
计算作业数很有用。e、 g:

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

wc-w明确定义计数器

#!/bin/bash
for f in files*; do
  (
    #commands
  )&
  (( i++ % 10 == 0 )) && wait
done
wait
无需初始化
i
,因为第一次使用它时它将默认为0。也无需重置该值,因为i=10、20、30等的
i%10
将为0。

xargs/parallel 另一个解决方案是使用为并发性设计的工具:

printf '%s\0' files* | xargs -0 -P6 -n1 yourScript
-P6
xargs
将启动的最大并发进程数。如果你愿意的话,就定为10

我建议使用
xargs
,因为它可能已经出现在您的系统中。如果您想要一个真正健壮的解决方案,请查看

数组中的文件名 对于您的问题的另一个明确答案:获取计数器作为数组索引

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*

如果你有Bash≥4.3,您可以使用
等待-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


这样做的好处是,我们对完成工作所需的时间不做任何假设。一个新的工作一旦有空间就开始了。此外,它都是纯Bash,因此不依赖外部工具,而且(可能更重要的是),您可以使用您的Bash环境(变量、函数等)而不导出它们(数组无法轻松导出,因此可能会成为一个巨大的专业版)。

您对文件做了什么?这“然后它就崩溃了”很难让人相信。我很确定它会产生一些错误信息。我喜欢这个。更便宜+1近一点看,我认为这不够方便,因为你可以有一个
I==10
,尽管后台作业的数量可以少于10个(它们可能会完成)。如果目标是让核心尽可能忙,我会使用类似于
parallel
的作业调度程序,而不是在
bash
中从头编写一个。这只是一种防止太多作业同时启动的方法,而不是让尽可能多的作业保持运行。在
bash
4.3中,您可以使用
wait-n
等待任何单个作业完成,然后再启动下一个作业。但是,这会受到竞争条件的影响(我认为是不可避免的),在这种情况下,
wait-n
在一个作业完成后被调用,这可能会导致一段时间内可以添加一个新作业,但我们正在等待另一个作业完成。如果您真的想让所有核心保持忙碌,请启动比一次可以运行的进程更多的进程,让操作系统来做调度。这对于I/O绑定的作业尤其可取,因为在其他进程可以运行时,这些作业可能处于空闲状态。另一个用于
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
#!/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