Копировать файлы из нескольких каталогов, если их имена указаны в списке, в один каталог.

Копировать файлы из нескольких каталогов, если их имена указаны в списке, в один каталог.

У меня есть огромное дерево папок, каждая из которых имеет несколько подкаталогов, расположенных примерно на 3 уровнях. Вот пример всего с одним уровнем:

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

2 directories, 4 files

У меня есть список имен файлов, которые меня интересуют, они называются spreadsheet.txt:

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

Я хотел бы скопировать все файлы, которые появляются в spreadsheet.txtв одну папку, например destination_folder. Любая помощь будет принята с благодарностью! Я предполагаю, что это будет включать findи cp, но, похоже, не могу это сделать,

решение1

Выполняется findодин раз для каждого имени файла и предполагается, что ни одно имя файла не содержит встроенных символов новой строки:

#!/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

Сначала он создает целевой каталог в текущем каталоге (и завершается, если это не удается). Затем он считывает входной файл, строка за строкой, и вызывает findдля поиска любого обычного файла с таким именем. Целевой каталог явно избегается с помощью findиспользования -pruneвсякий раз, когда мы сталкиваемся с ним в нашем поиске.

Всякий раз, когда файл с правильным именем найден, он копируется в целевой каталог. Если несколько файлов имеют одинаковое имя, копия в destination_folderбудет перезаписана.

Если текущий каталог огромен или список имен файлов длинный (но не много тысяч строк), то это будет медленная операция. Поэтому мы можем выбрать сделатьодинокийвызов find. Следующий код предполагает, что он выполняется с помощью eg, bashпоскольку он использует массивы:

#!/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 {} +

Здесь я также решил использовать cp -t(расширение GNU cp), чтобы иметь возможность вызывать функцию cpкак можно реже, а не один раз для каждого найденного файла.

Код выше создает массив, names, который в конечном итоге будет в правильном формате для использования с find. Команда, которая фактически выполняется в конце кода выше, учитывая пример в вашем вопросе, будет

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

Чтобы избежать проблем с конфликтами имен файлов в целевом каталоге, если вы используете GNU cp(например, в системе Linux), следует использовать cpс параметром -bили --backup.

В системах, отличных от Linux, GNU cpчасто может быть доступен gcpпосле установки GNU coreutils через менеджер пакетов.


Последний скрипт, но /bin/shбез массивов:

#!/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 {} +

решение2

Простой цикл for:

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

Связанный контент