如何复制文件列表并即时调整目标文件名?

如何复制文件列表并即时调整目标文件名?

当我不需要调整目标文件名时,我可以这样做:

$ 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 通配符(如bashorksh93**允许)更具体时)
  • 对于包含换行符的文件名也是安全的
  • 转换部分也非常灵活 - 例如,您可以使用类似的东西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如果您想继续执行其输出的操作,请删除对 的调用。

相关内容