当我不需要调整目标文件名时,我可以这样做:
$ find -type f -name '*.pat' -print0 | xargs -O cp -t /path/to/dest
它是安全的,因为文件名甚至可能包含换行符。
替代:
$ find -type f -name '*.pat' -print0 | cpio -p -0 -d /path/to/dest
现在我遇到的问题是目标是 VFAT 文件系统...因此文件名中不允许使用某些字符(例如“?”)。这意味着我必须调整目标文件名。
就像是
for i `find -type f -name '*.pat'` ; do
cp "$i" `echo $i | sed 's/?/_/'`
done
仅适用于没有空格的文件名 - 我可以将 IFS 更改为换行符 - 但如何将 '\0' 设置为 IFS?
而且 - for 循环会导致与文件一样多的 forks/exec(mv/sed) - 这比开头的两个示例所需的几个 forks/exec 要多得多。
解决该问题的替代方案有哪些?
答案1
pax
至少在 Debian、Suse、OpenBSD、NetBSD 上发现:
find . -type f -name '*.pat' -print0 | pax -0rws'/?/_/gp' /path/to/dest/
pax
是一个标准实用程序(与tar
或相反cpio
),但它的-0
选项不是,尽管可以在一些实现中找到。
如果同时存在?.pat
和_.pat
文件,它们最终将被替换为相同的名称,因此其中一个将覆盖目标中的另一个。同样,如果有_
和?
目录,它们的内容将合并到_
目标目录中。
使用 GNUsort
和 GNU uniq
,您可以预先检查冲突:
find . -type f -name '*.pat' -print0 |
tr '?' _ |
sort -z |
uniq -zd |
tr '\0' '\n'
它将报告冲突的文件(但不报告目录)。
您可以使用zsh
'zmv
来解决冲突,但这仍然意味着每个文件一mkdir
加一:cp
autoload zmv
mkdir-and-cp() {mkdir -p -- $3:h && cp $@}
zmv -n -Qp mkdir-and-cp '(**/)*.pat(D.)' '/path/to/dest/$f:gs/?/_/'
(高兴时删除-n
)。
答案2
可以使用 GNU tar 来实现:
$ find -type f -name '*.pat' -print0 | tar -c -f - --null --files-from - \
| tar -C /path/to/dest -v -x -f - --show-transformed --transform 's/?/_/g'
优点:
- 仅需要 3 个 fork/exec(与文件数量无关)
- 源文件的选择非常灵活 - 您可以使用 (GNU) find 的全部功能(即,当您需要比简单的 shell 通配符(如
bash
orksh93
的**
允许)更具体时) - 对于包含换行符的文件名也是安全的
- 转换部分也非常灵活 - 例如,您可以使用类似的东西
s/[^A-Za-z0-9 _-]/_/g
来替换所有不在字符类中的字符[A-Za-z0-9 _-]
答案3
解决这个问题的最佳方法是定义允许字符并绕过它们的转置,而不是寻求定义字符到转置。它比仅仅定义可接受的有限字符集更复杂,因为关于允许的字符集有一定的规则在哪里在文件名中。因此,这使用了一个应该是安全的有限子集(据我通过阅读 FAT32 文档得知)。像这样的东西应该可以工作(bash4+):
#!/bin/bash
shopt -s globstar nullglob
for file in **/*.pat; do
echo cp -t "${1:-.}" "${file//[![:alnum:].\-_\/]/_}"
done
这将返回类似这样的内容:
$ > bar\?.pat
$ > baz\*.pat
$ > foo.pat
$ ./script foo
cp -t baz*.pat foo/baz_.pat
cp -t bar?.pat foo/bar_.pat
cp -t foo.pat foo/foo.pat
将要复制到的目录作为第一个参数传递。echo
如果您想继续执行其输出的操作,请删除对 的调用。