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.txt
una sola carpeta, por ejemplo destination_folder
. ¡Cualquier ayuda recibida con mucha gratitud! Me imagino que implicará find
y cp
, pero parece que no puedo resolverlo.
Respuesta1
Ejecutando find
una 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 find
para localizar cualquier archivo normal que tenga ese nombre. El directorio de destino se find
evita explícitamente -prune
cuando 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_folder
se 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, bash
ya 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 cp
la menor cantidad de veces posible y no una vez por archivo encontrado.
El código anterior crea una matriz, names
que 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 cp
su opción -b
o --backup
.
En sistemas que no son Linux, GNU cp
a menudo puede estar disponible gcp
despué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