ファイルリストをコピーし、ターゲットファイル名を即座に調整するには?

ファイルリストをコピーし、ターゲットファイル名を即座に調整するには?

ターゲットファイル名を調整する必要がない場合は、次のことができます。

$ 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 ループはファイル (mv/sed) と同じくらい多くのフォーク/exec を発生させます。これは、最初の2つの例で必要とされるいくつかのフォーク/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存在する場合は、同じ名前に置き換えられるため、ターゲット上の1つが別のファイルを上書きします。同様に_、ディレクトリがある場合、その内容はターゲットディレクトリ?にマージされます。_

GNUsortとGNUを使用すると、uniq競合を事前に確認できます。

find . -type f -name '*.pat' -print0 |
  tr '?' _ |
  sort -z |
  uniq -zd |
  tr '\0' '\n'

競合するファイルを報告します(ディレクトリを除く)。

zsh'を使用してzmv競合を解決することはできますが、これはまだファイルごとにmkdir1プラス1を意味します。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つのフォーク/execのみが必要です(ファイル数に関係ありません)。
  • bashソースファイルの選択は非常に柔軟です。 (GNU)findのすべての機能を使用できます(たとえば、ksh93allowedなどの単純なシェルワイルドカードよりも具体的である必要がある場合**)。
  • 改行文字を含むファイル名にも安全です。
  • 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出力された操作を続行するには、への呼び出しを削除してください。

関連情報