从参数构建 find 命令

从参数构建 find 命令

我经常需要找到一个文件,但我不确定该文件的名称是什么,如下所示:

$ find -iname '*foo*' -o -iname '*bar*' -o -iname '*blah*'

这有点乏味。我想创建一个更易于使用的别名,如下所示:

$ findany foo bar blah

这是我的尝试:

findany() {
    args=("-iname" "'*$1*'")
    shift

    while [ $# -gt 0 ]; do
        args+=("-o" "-iname" "'*$1*'")
        shift
    done

    find ${args[@]}
}

问题是它永远不会产生任何结果,即使文件就在那里:

$ ls
bar.txt  blah.txt  foo.txt

$ findany foo bar blah
# nothing

如果我echo在命令前面添加,它看起来是正确的:

$ findany foo bar blah                     
find -iname '*foo*' -o -iname '*bar*' -o -iname '*blah*'

如果我复制上面的输出并运行它,它就可以正常工作:

$ find -iname '*foo*' -o -iname '*bar*' -o -iname '*blah*'
./bar.txt
./foo.txt
./blah.txt

我认为它与参数拆分或引号有关,但我不确定如何调试它。

我通常使用 zsh,但我在 bash 中验证了相同的行为。

答案1

你们非常接近!但由于某种原因,您在文件名本身中包含了单引号,因此它们永远不会匹配。

此外,您还必须引用"${args[@]}"才能使其正常工作。否则,它会受到分词的影响,并且任何 glob 都会在find看到它们之前在 shell 中扩展。

试试这个(特别是对于bash):

findany() {
    local args=('-iname' "*$1*")
    shift

    while [ "$#" -gt 0 ]; do
        args+=('-o' '-iname' "*$1*")
        shift
    done

    find . "${args[@]}"
}

答案2

使用 zsh,您可以使用:

set -o extendedglob # usually in your ~/.zshrc, here used for (#i) for
                    # case insensitive matching
print -rC1 -- **/*(#i)(foo|bar|blah)*(ND)

要从数组构建该模式,您可以使用j[|]参数扩展标志来连接数组的元素,并|与参数扩展标志结合使用, ~以便将其|视为全局模式运算符,或者与~参数扩展运算符结合使用,以便任何连接字符串中的通配符(即使是在数组元素中找到的通配符)也被视为模式。

findany1() print -rC1 -- **/*(#i)(${(~j[|])argv})*(ND)

或者:

findany2() print -rC1 -- **/*(#i)(${(j[|])~argv})*(ND)

例如findany1 '??'会查找名称包含的文件??,而findany2会查找名称至少包含 2 个字符的文件。您需要findany2 '\?\?'findany2 '[?][?]'查找名称??包含findany2.

为了与 做出同样的区分find,那就是:

findany2() (
  for arg do
    argv+=( -o -iname "$arg" )
    shift
  done
  shift
  find . "$@"
)
findany1() (
  set -o extendedglob
  for arg do
    argv+=( -o -iname "${arg//(#m)[][\\?*]/\\$MATCH}" )
    shift
  done
  shift
  find . "$@"
)

我们用( /支持的唯一引用运算符)转义find( ?*和)识别的通配符。[...]\\findfnmatch()

答案3

$ find -iname '*foo*' -o -iname '*bar*' -o -iname '*blah*'

这是解决问题的一种方法,但坦率地说,它的结果类似于

shopt -s globstar  ## enable recursive globbing operator **
shopt -s extglob   ## enable (|) pattern lists
shopt -s nocasematch  ## take a guess!

echo **/*@(foo|bar|blah)*

(但它不需要 的帮助就可以做到这一点find)。

我们可以很快地构建一个 shell 脚本。

#! /bin/bash -
shopt -s globstar  ## enable recursive globbing operator **
shopt -s extglob   ## enable (|) pattern lists
shopt -s nullglob  ## don't error if nothing matches
shopt -s nocasematch  ## take a guess!

IFS='|' # "$*" joins with the first character of IFS
pattern="**/*@(${*})*"

IFS= # do globbing but not splitting upon unquoted expansion:
matches=( $pattern )

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

如果您想将其作为函数,只需将其放入pattern=…函数done声明中即可。

相关内容