如何在 find 命令之后的 for 循环内使用 exec 命令?

如何在 find 命令之后的 for 循环内使用 exec 命令?

我已经尝试让它工作一段时间了。我想搜索文件列表,然后将它们全部复制到某个路径。

如果我单独运行该命令,则它的工作方式如下:

find <path> -name 'file1' -exec cp '{}' <path2> \;

但我无法在 for 循环内运行它。

#this works
for f in file1 file2 file3...; do find <path> -name "$f" \; done;
#none of these work (this code tries to find and copy the files named file and list from the path)
for f in file1 file2 file3...; do find <path> -name "$f" -exec cp '{}' <path2> \; done;
for f in file1 file2 file3...; do find <path> -name "$f" -exec cp {} <path2> \; done;

我尝试了其他一些方法,但不太可能奏效。代码引用中的第一行卡住了,其他行即使没有卡住也不会复制任何内容。

搜索之后,我无法在 for 循环中使用 exec 运行任何程序,此时我不知道该怎么做。

我已经通过搜索文件并将结果记录到另一个文件然后在 for 循环内单独运行副本解决了眼前的问题,但我仍然想知道如何做到这一点。

答案1

两个问题:

  1. for f in some_file不会迭代 的内容some_file,只会迭代或获取字符串some_file。要解决这个问题,请忘记使用for循环迭代文件内容,请使用正确实现的while构造。

  2. 在这种情况下,变量放在单引号内时不会被扩展'$f'。要实现你最初的想法,请使用双引号。

file_list把它们放在一起,假设文件名在文件内部以换行符分隔:

while IFS= read -r f; do 
    find /path1 -name "$f" -exec cp '{}' /path2 \;
done <file_list

或者,如果您知道文件位于目录中/path1,而不是在其任何子目录下,则可以使用数组来获取文件名,并cp直接使用(GNU),再次假设里面的文件名以换行符分隔file_list

( 
 IFS=$'\n'
 files=( $(<file_list) )
 cp -t /path2 "${files[@]}"
)

如果文件数量巨大,最好逐个进行迭代,cp而不是将其转储到数组中:

while IFS= read -r f; do cp -- "$f" /path2; done <file_list

file1 file2 file3 ...如果您在构造中直接有文件列表for,那么只需使用双引号就可以:

for f in file1 file2 file3; do 
    find /path1 -name "$f" -exec cp '{}' /path2 \;
done

现在,cp如果所有文件都不在任何子目录中,您也可以直接在这里使用:

cp -t /path2 file1 file2 file3

或者,如果您只想cp处理任何子目录下的任何文件,您可以提供静态绝对路径或相对路径。

答案2

您澄清说,您的文件列表不是文本文件,而是您想要用 查找的一系列文件名find。您提到这对您有用:

for f in file1 file2 file3 ... ; do find <path> -name "$f" \; done;

大概你的意思是你从该命令中获得了预期的文件列表。

然后你说这不管用:

for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done

假设您的意思是之前列出的文件没有复制到<path2>。我不确定您遇到了什么错误,但正如所写,我预计该命令会像这样挂起:

$for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done
>

等待丢失的;。假设您已更正该问题,我想不出任何其他明显的原因导致您的命令肯定会失败。你应该能够应付

for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; ; done

不过我建议使用-t标志来指定目录。我必须感谢大卫·福斯特为了此评论cp我认为这显示了使用 进行或mv调用的最佳形式find。它也会拒绝覆盖重复的文件。

for f in file1 file2 file3; do find /path/to/search -name "$f" -exec cp -vt /path/to/destination -- {} + ; done

笔记

  • -v详细地告诉我们正在做什么
  • -t使用指定目录作为目标
  • --不接受任何其他选择
  • +如果有多个匹配项(在您的情况下这不太可能,因为for将对文件列表中的每个项目运行一次,但每个名称可能有多个匹配的文件),然后构造一个参数列表,而cp不是运行cp多次
  • ;在 shell 中分隔命令。循环的形式for

    for var in things; do command "$var"; done
    

    如果没有看到;之前的内容done,bash 将等待;或中断信号。

答案3

虽然其他答案都是正确的,但我想提供一种不同的方法,不需要多次调用来find重复扫描相同的目录结构。基本思想是find使用

  1. 生成符合通用标准的文件列表,
  2. 应用自定义筛选加入该列表,并且
  3. cp例如,对过滤列表的条目执行某些操作。

实现1

(需要 Bash 读取空字节分隔的记录)

find /some/path -type f -print0 |
while read -rd '' f; do
  case "${f##*/}" in
    file1|file2|file3)
      printf '%s\0' "$f";;
  esac
done |
xargs -r0 -- cp -vt /some/other/path --

每个管道对应于上述三个步骤的下一步的开始。

case声明的优点是允许通配符匹配。或者,你可以使用 Bash 的条件表达式

if [[ "${f##*/}" == file1 || "${f##*/}" == file2 || "${f##*/}" == file3 ]]; then
  printf '%s\0' "$f"
fi

实施2

如果要匹配的文件名列表有点长,无法用一小组替换通配符模式,或者如果在写入时不知道要匹配的文件名列表,则可以求助于大批其中包含感兴趣的文件名列表:

FILE_NAMES=( "file1" "file2" "file3" ... )

find /some/path -type f -print0 |
while read -rd '' f; do
  for needle in "${FILE_NAMES[@]}"; do
    if [ "${f##*/}" = "$needle" ]; then
      printf '%s\0' "$f"
    fi
  done
done |
xargs -r0 -- cp -vt /some/other/path --

实施3

作为一种变体,我们可以使用关联数组希望其查找时间比普通的“列表”数组更快:

declare -A FILE_NAMES=( ["file1"]= ["file2"]= ["file3"]= ... )  # Note the superscripts with []=

find /some/path -type f -print0 |
while read -rd '' f; do
  if [ -v FILE_NAMES["${f##*/}"] ]; then
    printf '%s\0' "$f"
  fi
done |
xargs -r0 -- cp -vt /some/other/path --

相关内容