在 Bash 中并行填充变量

在 Bash 中并行填充变量

find我正在尝试利用并行化来加速访问多个硬盘驱动器上的文件的命令。不幸的是,要么忽略并行化,要么未填充变量。

found=""; IFS=$'\n'

for hdd in "${hdd_list[@]}"
do
    found+=$'\n'$(find "$hdd" -name "*filter*" -type f &) # ignores parellelization
    found+=$'\n'$(find "$hdd" -name "*filter*" -type f) & # doesn't fill variable
done

这是否可以在不诉诸临时文件的情况下实现?

答案1

命令替换等待从打开到命令内部的管道中获取所有数据,然后再继续。让 shell 在继续执行脚本的同时继续在后台读取数据将比必要的复杂得多。 (顺便说一句,这也意味着即使命令替换中的命令做了一些奇怪的事情,例如首先分叉并退出父级,当管道最终关闭时(如果确实如此),您仍然可以获得所有输出。)因此,将进程置于后台命令替换($(... &)没有做任何有用的事情。

将整个分配放入后台 ( foo=... &) 也不起作用,因为后台作业必须在单独的进程中运行,并且该后台进程无法更改主 shell 进程内存中的 shell 变量。

您可以安排将所有进程单独连接find到管道,让它们并行运行和打印,但管道缓冲区只有这么大,这意味着您也需要同时读取所有进程。这在 shell 中很难做到,因为你没有select(). (嗯,可能某些 shell 确实有它。)

但这一切都太复杂了,因为使用临时文件的简单解决方案就在那里。

如果您的find实现很好,因为它只写入整行(条目),并且您不关心顺序,则可以将所有输出重定向到单个文件:

f=$(mktemp)
for hdd in "${hdd_list[@]}"; do
    find "$hdd" ... &
done >> "$f"
# read "$f"
rm -f "$f"

但如果情况并非如此,或者您想确定,请为每个目录创建一个临时目录和一个输出文件find

d=$(mktemp -d)
i=1
for hdd in "${hdd_list[@]}"; do
    find "$hdd" > "$d/out$i.tmp" &
    i=$((i+1))
done
cat "$d"/*.tmp > "$d/all.out"
# read "$d/all.out"
rm -rf "$d"

当然,在第一个文件中,您可以跳过临时文件并直接从循环中读取。

由于您使用的是带有数组的 shell,因此您可能也希望将输出find放入数组中。例如,readarray在 Bash 中,这会将每一行放入不同的数组元素中:

readarray -t files < <(find ...)

答案2

parset为此而构建:

parset hd find {} -name \""*filter*"\" -type f ::: "${hdd_list[@]}"

但是,它确实使用临时文件。它们已为您清理干净,因此您无需处理它们。

查看更多:https://www.gnu.org/software/parallel/parset.html

答案3

看来您可能不理解使用“&”的全部含义。

野兽的本性对于后台进程来说,你不能从父进程访问后台子进程内容...(无论变量是否被声明为全局变量)。

您遇到的问题与其他问题中概述的相同发帖。从本质上讲,临时文件是规避该问题的唯一方法。

答案4

特别是对于并行化find,请查看fdfindfind这是默认情况下多线程的改进版本。

如果您无法使用fdfind,请尝试使用xargs,它会并行运行命令。借用自这个问题,你可以尝试这样的事情(虽然我不是 100% 的,所以你可能需要在它起作用之前进行修改):

found="$( printf "%s\0" "${hdd_list[@]}" | xargs -0 -I {} find {} -name "*filter*" -type f )"

怎么运行的:

printf "%s\0" "${hdd_list[@]}":打印出以空字符作为分隔符的列表

| xargs -0 -I {} ...:采用带有空分隔输入 ( -0) 的 stdin 并调用后面的命令。它将找到 的所有实例{}并将其替换为输入字段。

相关内容