将Bash脚本并行化

8
我需要计算多个网页中包含的整数之和。函数getPages()解析整数并将其赋值给$subTotal。在后台中,使用for循环调用getPages(),但如何得到$subTotal的总和呢?这是一个子shell问题吗?
到目前为止,我尝试了以下方法。
#!/bin/bash
total=0
getPages(){
  subTotal=$(lynx -dump http://"$(printf "%s:%s" $1 $2)"/file.html | awk -F, 'NR==1 {print $1}' | sed 's/\s//g')
  total=$(($total+$subTotal))
  echo "SubTotal: " $subTotal "Total: " $total
}
# /output/ SubTotal:  22 Total:  22
# /output/ SubTotal:  48 Total:  48   //Note Total should be 70

ARRAY=(
'pf2.server.com:6599'
'pf5.server.com:1199'
...
)

for server in ${ARRAY[@]} ; do
  KEY=${server%%:*}
  VALUE=${server##*:}
  getPages $KEY $VALUE &
done
wait
  echo $total
exit 0        

# /output/ 0

任何建议都将不胜感激。

在您的getPage()函数中执行return $subTotal,并在父shell中执行求和。 - Marc B
2
@Marc:你把Bash脚本和真正的编程语言搞混了。 - Ignacio Vazquez-Abrams
返回True,但因为只返回一个数值,所以这应该可以工作。 - Marc B
@Ignacio:Bash是一种真正的编程语言,只是没有多线程支持。 - Paŭlo Ebermann
1
@Marc:这里的问题在于并行执行。如果等待所有子进程,wait无法返回多个子进程的结果。 - Paŭlo Ebermann
2个回答

4
是的,这是一个子shell问题。在... &列表中执行的所有内容(即getPages $KEY $VALUE &)都在子shell中执行,这意味着那里的变量更改不会影响父shell。
我认为可以使用协程(即通过流进行通信),或者使用GNU parallelpexec来解决这个问题。

这是一个使用pexec的示例,使用默认输出来从单个进程进行通信。我使用了一个更简单的命令,因为您列出的服务器无法从此处访问。它会计算一些网页上的行数并将它们加起来。

ARRAY=(
   'www.gmx.de:80'
   'www.gmx.net:80'
   'www.gmx.at:80'
   'www.gmx.li:80'
)


(( total = 0 ))
while read subtotal
do
   (( total += subtotal ))
   echo "subtotal: $subtotal, total: $total"
done < <(
    pexec --normal-redirection --environment hostname --number ${#ARRAY[*]} \
     --parameters "${ARRAY[@]}" --shell-command -- '
     lynx -dump http://$hostname/index.html | wc -l'
    )

echo "total: $total"

我们在这里使用了一些技巧:
我们将并行进程的输出管道返回到主进程,在那里使用循环读取它。 为了避免为while循环创建子shell,我们使用bash的进程替换功能(<( ... ))以及输入重定向(<)而不是简单的管道。 我们在一个(( ... ))算术表达式命令中进行算术运算。我本可以使用let,但那样我就必须引用所有内容或避免空格。(您的total=$(( total + subtotal ))也可以工作。) pexec的选项:
  • --normal-redirection表示将所有子进程的所有输出流一起重定向到pexec的输出流中。(如果两个进程想同时写入可能会导致混乱。)
  • --environment hostname将每次执行的不同参数作为环境变量传递。否则它将成为简单的命令行参数。
  • --number ${#ARRAY[*]}(在我们的情况下得到--number 4)确保所有进程都将并行启动,而不仅仅是我们拥有的CPU数量或其他启发式方法。(这是针对网络往返受限的工作。对于CPU受限或带宽受限的工作,较小的数字更好。)
  • --shell-command确保该命令将被shell评估,而不是尝试直接执行它。这是由于其中的管道必须这样做。
  • --parameters "${ARRAY[@]}"列出实际参数-即数组的元素。对于每个参数,将启动命令的单独版本。
  • 在最后的--之后,命令作为单个'引号字符串出现,以避免外部shell过早解释其中的$hostname。该命令简单地下载文件并将其传输到wc -l,计算行数。

示例输出:

subtotal: 1120, total: 1120
subtotal: 968, total: 2088
subtotal: 1120, total: 3208
subtotal: 1120, total: 4328
total: 4328

这是运行时 ps -f 的部分输出:

 2799 pts/1    Ss     0:03  \_ bash
 5427 pts/1    S+     0:00      \_ /bin/bash ./download-test.sh
 5428 pts/1    S+     0:00          \_ /bin/bash ./download-test.sh
 5429 pts/1    S+     0:00              \_ pexec --number 4 --normal-redirection --environment hostname --parame...
 5430 pts/1    S+     0:00                  \_ /bin/sh -c ?     lynx -dump http://$hostname/index.html | wc -l
 5434 pts/1    S+     0:00                  |   \_ lynx -dump http://www.gmx.de:80/index.html
 5435 pts/1    S+     0:00                  |   \_ wc -l
 5431 pts/1    S+     0:00                  \_ /bin/sh -c ?     lynx -dump http://$hostname/index.html | wc -l
 5436 pts/1    S+     0:00                  |   \_ lynx -dump http://www.gmx.net:80/index.html
 5437 pts/1    S+     0:00                  |   \_ wc -l
 5432 pts/1    S+     0:00                  \_ /bin/sh -c ?     lynx -dump http://$hostname/index.html | wc -l
 5438 pts/1    S+     0:00                  |   \_ lynx -dump http://www.gmx.at:80/index.html
 5439 pts/1    S+     0:00                  |   \_ wc -l
 5433 pts/1    S+     0:00                  \_ /bin/sh -c ?     lynx -dump http://$hostname/index.html | wc -l
 5440 pts/1    S+     0:00                      \_ lynx -dump http://www.gmx.li:80/index.html
 5441 pts/1    S+     0:00                      \_ wc -l

我们可以看到,实际上所有的东西都是并行运行的,在我的单处理器系统上尽可能地实现。

0

使用GNU Parallel的简短版本:

ARRAY=(
  'www.gmx.de:80'
  'www.gmx.net:80'
  'www.gmx.at:80'
  'www.gmx.li:80'
)

parallel lynx -dump http://{}/index.html \| wc -l ::: "${ARRAY[@]}" | awk '{s+=$1} END {print s}'

如果主机:端口在文件中:
cat host_port | parallel lynx -dump http://{}/index.html \| wc -l | awk '{s+=$1} END {print s}'

了解更多:https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接