find 命令的转义问题

lon*_*nix 3 scripting bash find quoting

我需要找到目录中的所有内容,不包括某些子目录和文件。我的脚本需要将其作为函数调用:

function findStuff() {
  # define exclusions
  ignore_dirs=("$1" "*foo*")                        # exclude base dir
  ignore_files=("one.txt" "two.txt" "*three*.txt")
  # build patterns for find command
  dir_pattern=""
  file_pattern=""
  for i in "${ignore_dirs[@]}"; do dir_pattern=$dir_pattern" ! -path \"$i\""; done
  for i in "${ignore_files[@]}"; do file_pattern=$file_pattern" ! -name \"$i\""; done
  # find
  find "$1 $dir_pattern $file_pattern"
  # now do other stuff with the results...
}

findStuff /some/base/dir
Run Code Online (Sandbox Code Playgroud)

但这给了我一个No such file or directory错误。

所以我想看看命令实际上是什么,并尝试将echo find "$1 $dir_pattern $file_pattern"其粘贴到命令行上并且它起作用了。然后我将它粘贴到脚本中并运行它,它也起作用了!

所以我认为它因为一些逃避问题而失败。我做错了什么?

Kus*_*nda 8

find将使用第一个参数(最多为第一个参数是开头-或者说是!()是它得到顶级的路径进行搜索。find当您在函数中调用它时,您将给出一个参数,即字符串$1 $dir_pattern $file_pattern(扩展了变量)。找不到此路径。

您还在打算提供给的参数中包含文字双引号find。双引号是为了防止 shell 扩展全局模式和在空格(或IFS变量包含的任何内容)上进行拆分,但是如果您使用 eg,! -name \"thing\"那么双引号将成为find用于与文件名进行比较的模式的一部分。

使用数组,并find正确引用单独的参数:

myfind () {
  local ignore_paths=( "$1" "*foo*" )
  local ignore_names=( "one.txt" "two.txt" "*three*.txt" )

  local path_args=()
  for string in "${ignore_paths[@]}"; do
      path_args+=( ! -path "$string" )
  done

  local name_args=()
  for string in "${ignore_names[@]}"; do
      name_args+=( ! -name "$string" )
  done

  find "$1" "${path_args[@]}" "${name_args[@]}"
}
Run Code Online (Sandbox Code Playgroud)

每次我们追加到path_argsname_args上面时,我们都会向列表中添加三个元素,!-path-name, 和"$string"。展开"${path_args[@]}"and 时"${name_args[@]}"(注意双引号),元素将被单独引用。


等效实现适用于/bin/sh

myfind () (
    topdir=$1

    set --

    # paths to ignore
    for string in "$topdir" "*foo*"; do
        set -- "$@" ! -path "$string"
    done

    # names to ignore
    for string in "one.txt" "two.txt" "*three*.txt"; do
        set -- "$@" ! -name "$string"
    done

    find "$topdir" "$@"
)
Run Code Online (Sandbox Code Playgroud)

shshell 中,我们只有一个数组可供我们使用,它是位置参数列表$@,因此我们find在其中收集我们的选项。该bash特异性的解决方案也被写入到使用单一数组,显然和sh变化将运行bash了。


最后,您的echo测试输出不是您的函数将执行的命令的准确表示。

考虑一下:

cat "my file name"
Run Code Online (Sandbox Code Playgroud)

它运行cat在一个叫做 的东西上my file name,并且

echo cat "my file name"
Run Code Online (Sandbox Code Playgroud)

输出字符串cat my file name。这是因为 shell 在执行命令之前删除了字符串周围的引号。运行命令,cat将查找三个文件,而不是一个。

当您将其复制粘贴到 shell 中时,您的命令运行良好,因为您在输出的字符串中包含了文字双引号echo(通过转义它们),但这不是您的函数执行的实际命令。