Copie archivos de varios directorios, si su nombre aparece en una lista, en un solo directorio

Copie archivos de varios directorios, si su nombre aparece en una lista, en un solo directorio

Tengo un enorme árbol de carpetas, cada una con múltiples subdirectorios que abarcan alrededor de 3 niveles. A continuación se muestra un ejemplo con un solo nivel:

$ tree
.
|-- AB.txt
|-- CD.txt
|-- destination_folder
|-- spreadsheet.txt
`-- subdirectory
    `-- EF.txt

2 directories, 4 files

Tengo una lista de nombres de archivos que me interesan, llamada spreadsheet.txt:

$ cat spreadsheet.txt 
AB.txt
CD.txt
EF.txt

Me gustaría copiar todos los archivos que aparecen en spreadsheet.txtuna sola carpeta, por ejemplo destination_folder. ¡Cualquier ayuda recibida con mucha gratitud! Me imagino que implicará findy cp, pero parece que no puedo resolverlo.

Respuesta1

Ejecutando finduna vez por nombre de archivo y suponiendo que ningún nombre de archivo contenga nuevas líneas incrustadas:

#!/bin/sh

mkdir -p destination_folder || exit 1
while IFS= read -r name; do
    find . -path ./destination_folder -prune -o \
        -type f -name "$name" -exec cp {} destination_folder \;
done <spreadsheet.txt

Esto primero crea el directorio de destino en el directorio actual (y finaliza si eso falla). Luego lee el archivo de entrada, línea por línea, y llama findpara localizar cualquier archivo normal que tenga ese nombre. El directorio de destino se findevita explícitamente -prunecuando lo encontramos en nuestra búsqueda.

Siempre que se encuentra un archivo con el nombre correcto, se copia al directorio de destino. Si varios archivos tienen el mismo nombre, destination_folderse sobrescribirá la copia .

Si el directorio actual es enorme, o si la lista de nombres de archivos es larga (pero no muchos miles de líneas), entonces será una operación lenta. Por lo tanto, podemos optar por hacer unasolterollamar a find. El siguiente código supone que se ejecuta, por ejemplo, bashya que utiliza matrices:

#!/bin/bash

mkdir -p destination_folder || exit 1

names=()
while IFS= read -r name; do
    names+=( -o -name "$name" )
done <spreadsheet.txt

find . -path ./destination_folder -prune -o \
    \( "${names[@]:1}" \) -type f -exec cp -t destination_folder {} +

Aquí, también elegí usar cp -t(una extensión GNU cp) para poder llamar cpla menor cantidad de veces posible y no una vez por archivo encontrado.

El código anterior crea una matriz, namesque eventualmente tendrá el formato correcto para usar con find. El comando que realmente se ejecuta al final del código anterior, dado el ejemplo de su pregunta, será

find . -path ./destination_folder -prune -o '(' -name AB.txt -o -name CD.txt -o -name EF.txt ')' -type f -exec cp -t destination_folder '{}' +

Para evitar el problema de colisiones de nombres de archivos en el directorio de destino, si está utilizando GNU cp(por ejemplo, en un sistema Linux), debe utilizar cpsu opción -bo --backup.

En sistemas que no son Linux, GNU cpa menudo puede estar disponible gcpdespués de instalar GNU coreutils a través de un administrador de paquetes.


Ese último script, pero para /bin/sh(sin matrices):

#!/bin/sh

mkdir -p destination_folder || exit 1

set --
while IFS= read -r name; do
    set -- -o -name "$name" "$@"
done <spreadsheet.txt

shift

find . -path ./destination_folder -prune -o \
    \( "$@" \) -type f -exec cp -t destination_folder {} +

Respuesta2

Un bucle for simple:

for filename in $(cat spreadsheet.txt)
do
find . -name "$filename" -exec cp {} /destination/folder \;
done

información relacionada