查找:如何有效地搜索大文件名列表

查找:如何有效地搜索大文件名列表

我需要找到几百个文件,其中的基本名称由某个列表提供(我们称之为baseNames)。然后我需要搜索这些基本名称+三个给定的扩展名。

例子:假设从输入列表中提取的基本名称之一是,FOO并且给定的扩展名是.txt, .csv, .py。然后我需要找到FOO.txt,,FOO.csvFOO.py

我的 bash 脚本中当前的方法如下:

for bn in ${baseNames}; do
  find ${searchDir} '(' -name "$bn.txt" -o -name "$bn.csv" -o -name "$bn.py" ')'
done

这可行,但效率很低。对于每个基本名称,我需要再次运行find整体searchDir,其中包含相当多的文件,因此需要一段时间。

find有没有办法通过选项或管道提供应搜索的文件列表?

显然我知道-name ... -or,但如果我有几百个文件,这种方法显然是不现实的。为了简单起见,您也可以忽略扩展。假设我有一个巨大的文件列表,我想要搜索这些文件作为find.

答案1

使用数组。例如

#!/bin/bash

baseNames=(FOO BAR BAZ)

findNames=('(')
for bn in "${baseNames[@]}"; do
  for ext in txt csv py; do
    findNames+=("$bn.$ext" '-o' '-name')
  done
done
# replace the final '-o' and '-name' in the array with a close parenthesis
unset 'findNames[-1]'
findNames[-1]=')'
# If using a version of bash before v4.3, use:
#unset 'findNames[${#findNames[@]}-1]'
#findNames[${#findNames[@]}-1]=')'


declare -p findNames

is的输出declare -p(添加了一些换行符和空格以将其分解并使其更具可读性):

declare -a findNames=(
  [0]="("
    [1]="-name" [2]="FOO.txt" [3]="-o" [4]="-name" [5]="FOO.csv"
    [6]="-o" [7]="-name" [8]="FOO.py" [9]="-o" [10]="-name" [11]="BAR.txt"
    [12]="-o" [13]="-name" [14]="BAR.csv" [15]="-o" [16]="-name" [17]="BAR.py"
    [18]="-o" [19]="-name" [20]="BAZ.txt" [21]="-o" [22]="-name" [23]="BAZ.csv"
    [24]="-o" [25]="-name" [26]="BAZ.py"
  [27]=")"
)

要将数组与 一起使用find,您需要执行以下操作:

searchDir="./"
find "$searchDir" "${findNames[@]}"

这将导致执行以下 find 命令(为了便于阅读而添加换行符):

find ./ ( -name FOO.txt -o -name FOO.csv -o -name FOO.py \
  -o -name BAR.txt -o -name BAR.csv -o -name BAR.py \
  -o -name BAZ.txt -o -name BAZ.csv -o -name BAZ.py )

和不需要在这里转义,因为 shell 将它们视为文字参数(数组已被 bash 扩展),而不是启动子 shell 的指令()如果您将它们输入 shell,则必须转义或引用它们。

答案2

以下sh脚本从文件中读取基本名称names,该文件每行包含一个名称(如果名称包含空格等,则应加引号),并调用一批包含这些名称的内联脚本sh -c(一次 50 个)。我将输入分成批次,以防万一数据扩展到对于单次调用来说太长的列表find(我们需要构造总组合长度超过n输入数据长度时间的命令,其中n是要查找的文件名后缀的数量)。

内联脚本-name根据给定的基本名称构建测试的“OR 列表” find。每个基本名称都输入到列表中,其中包含三个文件名后缀.txt.csv和的变体.py

该列表保存在位置参数列表 中"$@"

列表完成后,find调用该函数来查找某个目录中或下的与这些名称匹配的常规文件$topdir

topdir=$HOME

<names xargs -L 50 sh -c '
        topdir=$1; shift

        for name do
                for suffix in txt csv py; do
                        set -- "$@" -o -name "$name.$suffix"
                done
                shift  # shift off current base name
        done
        shift  # shift off the initial "-o"

        find "$topdir" -type f \( "$@" \) -print
' sh "$topdir"

使用小于 50 的数字运行并使用sh -x -c代替,以sh -c查看内联脚本实际执行的命令。


如果您想使用命名数组和bashshell:

topdir=$HOME

<names xargs -L 50 bash -c '
        topdir=$1; shift
        unset tests

        for name do
                for suffix in txt csv py; do
                        tests+=( -o -name "$name.$suffix" )
                done
        done

        find "$topdir" -type f \( "${tests[@]:1}" \) -print
' bash "$topdir"

这里,使用数组tests代替位置参数列表。看起来奇怪的"${tests[@]:1}"扩展到数组元素列表,除了第一个元素(将是-o)。

不过,如果您使用bash,您也可以使用它的通配工具(最初从 shell 继承ksh):

shopt -s extglob globstar dotglob nullglob

topdir=$HOME

printf -v pattern '%s/**/@(%s).@(txt|csv|py)' "$topdir" "$(paste -s -d '|' - <names)"

eval "pathnames=( $pattern )"

# The following loop is only for illustration.
# If you really just wanted to list the names, use
#     printf '%s\n' "${pathnames[@]}"

for pathname in "${pathnames[@]}"; do
        printf '%s\n' "$pathname"
done

这会根据文件的内容构建扩展的通配模式names。这种模式最终可能看起来像

/home/myself/**/@(name1|name2|name3).@(txt|csv|py)

...这将与您可能感兴趣的名称相匹配。请注意,您必须在循环中自己进行任何文件类型测试(以从目录等中筛选出常规文件)

在脚本顶部设置的 shell 选项允许使用扩展模式@(...|...)( extglob),使用**向下匹配到子目录 ( globstar),允许我们隐藏或位于隐藏子目录 ( dotglob) 中的名称。nullglob如果根本没有匹配项,我们还设置ta 使模式消失。

答案3

使用 zsh(当您将这些参数扩展不加引号时,您将在代码中使用 zsh 语法):

names=(foo bar baz)
exts=(txt csv py)
print -rC1 - **/(${(~j[|])names}).${(~j[|])exts})(ND)

其中,使用 来${(j[|])array}连接数组的元素||由于 ,a 被视为全局运算符~Nnullglob,Ddotglob.

或者当然直接这样做:

print -rC1 - **/(foo|bar|baz).(cvs|py|txt)(ND)

如果在某些文件中每行找到一个名称和扩展名,请使用

names=( ${(f)"$(< names.txt)"} )
 exts=( ${(f)"$(< exts.txt)"}  )

你还可以这样做:

print -rC1 - **/$^names.$^exts(ND)

但这会降低效率,因为这会为 name + ext 的每个组合扩展一个递归 glob。

用于find进行搜索:

cmd=(find . '(') or=()
for name ($^names.$^exts) cmd+=($or -name ${(b)name}) or=(-o)
cmd+=(')')
$cmd

相关内容