我需要找到几百个文件,其中的基本名称由某个列表提供(我们称之为baseNames
)。然后我需要搜索这些基本名称+三个给定的扩展名。
例子:假设从输入列表中提取的基本名称之一是,FOO
并且给定的扩展名是.txt
, .csv
, .py
。然后我需要找到FOO.txt
,,FOO.csv
。FOO.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
查看内联脚本实际执行的命令。
如果您想使用命名数组和bash
shell:
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 被视为全局运算符~
。N
为nullglob
,D
为dotglob
.
或者当然直接这样做:
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