我已经尝试让它工作一段时间了。我想搜索文件列表,然后将它们全部复制到某个路径。
如果我单独运行该命令,则它的工作方式如下:
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
两个问题:
for f in some_file
不会迭代 的内容some_file
,只会迭代或获取字符串some_file
。要解决这个问题,请忘记使用for
循环迭代文件内容,请使用正确实现的while
构造。在这种情况下,变量放在单引号内时不会被扩展
'$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
使用
- 生成符合通用标准的文件列表,
- 应用自定义筛选加入该列表,并且
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 --