Como copiar uma lista de arquivos e ajustar nomes de arquivos dinamicamente?

2

Quando não preciso ajustar nomes de arquivos de destino, posso fazer algo assim:

$ find -type f -name '*.pat' -print0  | xargs -O cp -t /path/to/dest

É seguro, porque os nomes dos arquivos podem conter até caracteres de nova linha.

Uma alternativa:

$ find -type f -name '*.pat' -print0 | cpio -p -0 -d /path/to/dest

Agora eu tenho o problema que o destino é um sistema de arquivos VFAT ... assim, certos caracteres não são permitidos em nomes de arquivos (por exemplo, '?'). Isso significa que eu tenho que ajustar os nomes dos arquivos de destino.

Algo como

for i 'find -type f -name '*.pat'' ; do
    cp "$i" 'echo $i | sed 's/?/_/''
done

funciona apenas para nomes de arquivos sem espaços - eu poderia alterar o IFS apenas para nova linha - mas como definir '\ 0' como IFS?

E ainda - o loop for leva a tanto forks / execs (de mv / sed) quanto os arquivos - o que é muito mais excessivo do que os poucos forks / execs necessários para os dois exemplos no começo.

Quais são as alternativas para resolver esse problema?

    
por maxschlepzig 24.02.2013 / 11:09

3 respostas

3

Com pax como encontrado no Debian, Suse, OpenBSD, NetBSD pelo menos:

find . -type f -name '*.pat' -print0 | pax -0rws'/?/_/gp' /path/to/dest/

pax é um utilitário padrão (ao contrário de tar ou cpio ), mas sua opção -0 não é, embora possa ser encontrada em algumas implementações.

Se houver dois arquivos ?.pat e _.pat , eles serão substituídos pelo mesmo nome para que um substitua o outro no destino. Mesmo se houver um diretório _ e ? , o conteúdo deles será mesclado dentro do diretório _ no destino.

Com o GNU sort e o GNU uniq , você pode verificar os conflitos antecipadamente com:

find . -type f -name '*.pat' -print0 |
  tr '?' _ |
  sort -z |
  uniq -zd |
  tr '
autoload zmv
mkdir-and-cp() {mkdir -p -- $3:h && cp $@}
zmv -n -Qp mkdir-and-cp '(**/)*.pat(D.)' '/path/to/dest/$f:gs/?/_/'
' '\n'

Qual seria o relatório de arquivos conflitantes (mas não diretórios).

Você pode usar zsh zmv , o que resolveria os conflitos, mas isso ainda significaria um mkdir e um cp por arquivo:

find . -type f -name '*.pat' -print0 | pax -0rws'/?/_/gp' /path/to/dest/

(remova -n quando feliz).

    
por 24.02.2013 / 22:06
3

Pode-se usar o GNU Tar para isso:

$ 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'

Vantagens:

  • apenas 3 forks / execs necessários (independente do número de arquivos)
  • a seleção de arquivos de origem é muito flexível - você pode usar todo o poder do (GNU) find para isso (ou seja, quando precisar ser mais específico do que um simples shell globbing como bash ou ksh93 \ ** permite)
  • também é seguro para nomes de arquivos que contêm novas linhas
  • a parte de transformação também é muito flexível - por exemplo, você pode usar algo como s/[^A-Za-z0-9 _-]/_/g , que substitui todos os caracteres que não estão na classe de caractere [A-Za-z0-9 _-]
por 24.02.2013 / 19:04
1

A melhor maneira de abordar isso é definir os caracteres permitidos e ignorar a transposição para eles em vez de procurar definir os caracteres para transpor. É mais complexo do que apenas definir um conjunto de caracteres limitado, o que é aceitável, porque há certas regras sobre o que é permitido onde em um nome de arquivo. Como tal, isso usa um subconjunto limitado que deve ser seguro (tanto quanto eu posso dizer ao ler documentos FAT32). Algo como isso deve funcionar (bash4 +):

#!/bin/bash

shopt -s globstar nullglob

for file in **/*.pat; do
    echo cp -t "${1:-.}" "${file//[![:alnum:].\-_\/]/_}"
done

Isso retornará algo assim:

$ > 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

Passe o dir para copiar como o primeiro argumento. Remova a chamada para echo se você quiser ir em frente e fazer o que ela gera.

    
por 24.02.2013 / 11:54