Localizar e copiar diretórios contendo tipo de arquivo

4

Eu tenho um diretório "Movies" contendo subdiretórios "Movie Name". Cada subdiretório "Movie Name" contém um arquivo de filme e arquivos de imagem / nfo relacionados, etc.

Estou tentando copiar todos os diretórios contendo o arquivo de filme do tipo ".avi" para um dispositivo usb externo.

Portanto, se Directory/subdirectory A contiver movie.avi , poster.jpg e movie.nfo , eu quero copiar esse diretório e seu conteúdo para a unidade externa.

Eu tentei isso:

find . -name "*.avi" -printf "%h\n" -exec cp {} /share/USBDisk1/Movies/ \;

Mas apenas copia o arquivo, não o diretório e o conteúdo do arquivo.

    
por Lowey 13.05.2013 / 07:15

5 respostas

5

Primeiro, por que sua tentativa não funciona: -printf "%h\n" imprime a parte do diretório do nome do arquivo .avi . Isso não afeta nada na ação -exec subseqüente - {} não significa “seja qual for o último comando printf impresso”, significa “o caminho para o arquivo encontrado”.

Se você quiser usar a parte do diretório do nome do arquivo nesse comando cp , precisará modificar o nome do arquivo antes de passá-lo para cp . O comando find não tem esse recurso, mas um shell faz isso, portanto, faça find invocar um shell que chama cp .

find . -name "*.avi" -exec sh -c 'cp -Rp "${0%/*}" /share/USBDisk1/Movies/' {} \;

Você precisará passar -r para cp , já que está copiando um diretório. Você provavelmente deve preservar os metadados, como o tempo de modificação dos arquivos, também, com -p .

Você pode substituir cp -Rp por rsync -a . Dessa forma, se você já copiou um diretório de filmes, ele não será copiado novamente (a menos que seu conteúdo tenha sido alterado).

Seu comando tem um defeito que pode ou não afetar você: se um diretório contiver vários arquivos .avi , ele será copiado várias vezes. Seria melhor procurar diretórios e copiá-los se eles contiverem um arquivo .avi , em vez de procurar .avi files. Se você usar rsync em vez de cp , os arquivos não serão copiados novamente, é só um pouco mais de trabalho para rsync verificar a existência dos arquivos repetidamente.

Se todos os diretórios de filme estiverem imediatamente abaixo do diretório de nível superior, você não precisará de find , um loop simples sobre um padrão curinga é suficiente.

for d in ./*/; do
  set -- "$d/"*.avi
  if [ -e "$1" ]; then
    # there is at least one .avi file in $d
    cp -rp -- "$d" /share/USBDisk1/Movies/
  fi
done

Se os diretórios de filmes podem ser aninhados (por exemplo, Movies/science fiction/Ridley Scott/Blade Runner ), você não precisa de find , um loop simples sobre um padrão de caractere curinga é suficiente. Você precisa estar executando ksh93 ou bash ≥4 ou zsh, e no ksh93 você precisa executar set -o globstar primeiro, e no bash você precisa executar shopt -s globstar primeiro. O padrão curinga **/ corresponde ao diretório atual e a todos os seus subdiretórios recursivamente (o bash também percorre links simbólicos para subdiretórios).

for d in ./**/; do
  set -- "$d/"*.avi
  if [ -e "$1" ]; then
    cp -rp -- "$d" /share/USBDisk1/Movies/
  fi
done
    
por 14.05.2013 / 01:35
1

Já que você parece estar usando ferramentas GNU, você pode fazer:

find . -name '*.avi' -printf '%h
pax -0rw /share/USBDisk1/Movies/
' | tr '/' '/' | LC_ALL=C sort -zu | tr '/' '/' | awk -vRS='
find . -type d -has '*.avi' -prune -exec cp -rt /target {} +
' -vORS='
find . -type d -exec bash -c '
  shopt -s nullglob dotglob
  set -- "$1"/*.avi
  (( $# > 0 ))' sh {} \; \
  -prune -exec cp -r {} /share/USBDisk1/Movies/ \;
' ' NR>1 && substr($0, 1, length(l)) == l {next} {print; l=$0"/"}' | xargs -r0 cp -rt /share/USBDisk1/Movies/

O acima é específico do GNU:

  • para find (por causa de -printf )
  • para sort devido a -z
  • para awk para a capacidade de lidar com caracteres NUL
  • para xargs (para as opções -r e -0 , embora alguns BSDs também os suportem)
  • cp (para -t )

A ideia é:

  • find gera os dirnames de todos os arquivos avi .
  • classificamos essa lista para que cada diretório seja listado após seu diretório pai (para isso, precisamos que o caractere / seja classificado antes de qualquer outro, e é por isso que o trocamos por ).
  • usamos sort -u para que cada diretório apareça apenas uma vez
  • usamos um pequeno script awk para remover todas as entradas de diretório para as quais um ancestral já tenha sido incluído (já que não queremos copiar ./a e ./a/b se ambos contiverem arquivos AVI).
  • passe essa lista para a stdin de xargs , para que ela possa ser passada como argumentos para cp -t target .

Observe que ele não tenta proteger contra possíveis conflitos se alguns diretórios tiverem o mesmo nome.

Se você deseja reproduzir a estrutura de diretórios na unidade de destino, é possível substituir xargs -r0 cp -rt /share/USBDisk1/Movies/ por

find . -name '*.avi' -printf '%h
pax -0rw /share/USBDisk1/Movies/
' | tr '/' '/' | LC_ALL=C sort -zu | tr '/' '/' | awk -vRS='
find . -type d -has '*.avi' -prune -exec cp -rt /target {} +
' -vORS='
find . -type d -exec bash -c '
  shopt -s nullglob dotglob
  set -- "$1"/*.avi
  (( $# > 0 ))' sh {} \; \
  -prune -exec cp -r {} /share/USBDisk1/Movies/ \;
' ' NR>1 && substr($0, 1, length(l)) == l {next} {print; l=$0"/"}' | xargs -r0 cp -rt /share/USBDisk1/Movies/

(com pax suportando a opção -0 como a encontrada no Debian ou pelo menos no MirBSD).

Idealmente, você gostaria de poder escrevê-lo assim:

%pre%

Infelizmente, find não possui esse predicado -has . O mais próximo que você pode conseguir seria:

%pre%

Mas isso significa chamar um bash e refazer a listagem de arquivos em bash para cada diretório, então provavelmente seria menos eficiente que a solução anterior.

    
por 13.05.2013 / 15:21
0
foo () 
{ 
    local subdirs=() target="$1" dest="$2";
    while IFS= read -rd ''; do
        subdirs+=("$REPLY");
    done < <(find "$target" -type f -iname '*.avi' -exec bash -c 'printf "%s
foo () 
{ 
    local subdirs=() target="$1" dest="$2";
    while IFS= read -rd ''; do
        subdirs+=("$REPLY");
    done < <(find "$target" -type f -iname '*.avi' -exec bash -c 'printf "%s%pre%" "${@%/*}"' _ {} + | sort -zu);
    cp -rp -- "${subdirs[@]}" "$dest"
}
" "${@%/*}"' _ {} + | sort -zu); cp -rp -- "${subdirs[@]}" "$dest" }

Uso: foo <source directory> <destination directory>

Que também abordará a seguinte questão, mencionada anteriormente por Gilles:

Your command has a defect that may or may not affect you: if a directory contains multiple .avi files, it will be copied multiple times. It would be better to look for directories, and copy them if they contain a .avi file, rather than look for .avi files.

    
por 14.05.2013 / 02:17
0

Eu encontrei este que eu acho que é muito mais simples e resulta no que você precisa:

find . -name "*.avi" -exec cp "{}" /share/USBDisk1/Movies/ \;

Eu acho o mesmo que você fez, mas sem o -printf , pois o "{}" captura a saída do find.

Referência: link

Melhor,

    
por 02.09.2014 / 04:21
-2

Você pode usar xargs e cp para fazer o trabalho:

find . -name "*.avi" -printf "%h\n" | xargs cp -t /share/USBDisk1/Movies/ -r

    
por 13.05.2013 / 07:34