Переберите каталоги и скопируйте все файлы из любых подкаталогов в новый каталог с тем же именем.

Вопрос или проблема

Я постараюсь объяснить это, насколько смогу. У меня есть несколько сотен директорий, каждая из которых содержит несколько других поддиректорий и файлы, разбросанные по этим поддиректориям. Мне не нужны и не нужны поддиректории, только файлы, но названия директорий должны остаться прежними. Таким образом, я хотел бы перейти от:

$ исходная структура

├── sub1
│   └── sub1.1
│       ├── file1
│       └── file2
├── sub2
    └── sub2.1
        └── sub2.1.1
            ├── file1
            └── file2

к новой структуре:

├── sub1
│     ├── file1
│     └── file2
├── sub2
      ├── file1
      └── file2

Всё, что действительно важно, это чтобы папки, здесь sub1 & sub2, остались с теми же именами; все поддиректории внутри них не копируются, и все файлы копируются в новую папку. Я пытался выяснить это около 2 часов и, кажется, не могу этого сделать.

Я использую bash и знаю, что это возможно, я просто не знаю, как к этому прийти. Спасибо за любую помощь!

Вы можете передать find через xargs, выполняя cp:

find sub1/* -type f -mindepth 2 -print0 | xargs -0 cp -t /sub1

Моя первая идея была в том, чтобы скопировать всю структуру в новое место, cd туда и перестроить новую структуру директорий. Но файл типа ./sub2/foo/foo вызвал бы проблему: он должен стать ./sub2/foo, но это имя занято директорией, которую нельзя удалить, прежде чем мы переместим файл.

Построим новую структуру во время копирования.


Решение для bash, использующее нестандартные опции

#!/bin/bash

srce="/source/directory"
trgt="/target/directory"

shopt -s nullglob dotglob

mkdir -p -- "$trgt" || exit 1

# в случае, если trgt относительный
cd -- "$trgt" || exit 1
trgt="$PWD"
cd -- "$OLDPWD" || exit 2

cd -- "$srce" || exit 2

find . -maxdepth 1 ! -type d -exec cp -t "$trgt" -- {} +

for d in ./*; do
   [ -d "$d" ] && mkdir -p -- "$trgt/$d" \
   && find "$d" ! -type d -exec cp -t "$trgt/$d" -- {} +
done

Примечания:

  • Недиректории, которые существуют непосредственно в исходной директории, будут скопированы напрямую в целевую директорию.
  • Поддиректории без файлов (если таковые имеются) будут созданы в любом случае.
  • Существующее содержимое в целевой директории может привести к неправильной работе кода.
  • Конфликты имен (если таковые имеются) не обрабатываются.
  • Двойной дефис объяснен здесь.
  • Непереносимые фрагменты: shopt, find -maxdepth, cp -t.

Переносимое решение

(Я думаю, что оно переносимое. Если это не так, пожалуйста, оставьте комментарий.)

#!/bin/sh

srce="/source/directory"
trgt="/target/directory"

mkdir -p -- "$trgt" || exit 1

# в случае, если trgt относительный
cd -- "$trgt" || exit 1
trgt="$PWD"
cd -- "$OLDPWD" || exit 2

cd -- "$srce" || exit 2

find . \
   ! -type d \
   \( \( ! -path '*/*/*' -exec cp -- {} "$trgt/" \; \) \
   -o \( -path '*/*/*' -exec sh -c '
      trgt="$1"
      shift
      for f do
         sbdr="${f#./}"
         sbdr="${sbdr%%/*}"
         mkdir -p -- "$trgt/$sbdr" && cp -- "$f" "$trgt/$sbdr"
      done
   ' find-sh "$trgt" {} + \) \)

Примечания:

  • Этот подход существенно отличается, он не использует цикл for.
  • Недиректории, которые существуют непосредственно в исходной директории, будут скопированы напрямую в целевую директорию.
  • Поддиректории без файлов не будут созданы. Здесь два решения различаются.
  • Существующее содержимое в целевой директории может привести к неправильной работе кода.
  • mkdir -p используется чрезмерно. С помощью дополнительной логики можно, безусловно, уменьшить количество mkdir, но я выбрал KISS.
  • cp используется весьма неэффективно: один процесс на файл.
  • Конфликты имен (если таковые имеются) не обрабатываются. Два разных решения могут сохранять разные конфликтные файлы.

Ещё один подход мог бы заключаться в том, чтобы находить подкаталоги с одним find и запускать дополнительные find в них для обнаружения файлов. Это возможно, но сложно. Сравните этот мой ответ, фрагмент “Как правильно запускать find на результатах другого find?”

Моё решение немного сложнее, но, возможно, его легче следовать?

> tree
.
├── filebot.txt
├── test
│   ├── dir1
│   │   ├── foo1
│   │   └── foo2
│   └── dir2
│       └── foo3
├── test2
│   ├── dir2
│   │   └── foo3
│   └── dir3
│       ├── foo1
│       └── foo2
└── test3
    ├── dir1
    │   ├── foo1
    │   └── foo3
    └── dir3
        └── foo2

команда

> mapfile -t files < <(find . -mindepth 2 -type f );\
 for FILE in ${files[*]}; do \
   mv "$FILE" $(sed -n 's/\(.\/.*\/\).*\//\1/p' <<< "$FILE");\
 done

объяснение

  • mapfile: создание массива файлов для обработки
  • цикл for: обработка каждого файла в массиве
  • mv/cp/rsync: действие для каждого файла
    • sed: захват основного имени каталога и удаление поддиректорий.

результаты

mv ./test/dir1/foo1 ./test/foo1
mv ./test/dir1/foo2 ./test/foo2
mv ./test/dir2/foo3 ./test/foo3
mv ./test2/dir2/foo3 ./test2/foo3
mv ./test2/dir3/foo1 ./test2/foo1
mv ./test2/dir3/foo2 ./test2/foo2
mv ./test3/dir1/foo1 ./test3/foo1
mv ./test3/dir1/foo3 ./test3/foo3
mv ./test3/dir3/foo2 ./test3/foo2

Вы можете очистить пустые директории с помощью

find -type d -empty -delete

Эта команда даст вам список всех файлов, расположенных в подкаталогах вашего указанного корневого каталога:

find ./*/ -type f -iname "*.*"

Затем вы могли бы использовать basename для извлечения самого имени файла, затем перебрать этот вывод с использованием rsync (лучше, чем cp, потому что он проверяет свою работу). Что-то вроде этого, возможно:

rsync -aiSP /source/"${filepath}" /destination/"${basefilename}"

Структурно что-то вроде этого:

for filepath in "$( find ./*/ -type f -iname "*.*" )" ; do 
    basefilename="$( basename "${filepath}" )" 
    rsync -aiSP /source/"${filepath}" /destination/"${basefilename}" 
done  

Этот код не будет работать из-за того, как find обрабатывается, но что-то подобное должно делать то, о чём вы спрашиваете.

Основные выводы для меня — это find ./*/, который избегает несвязанных файлов в корневой папке и ищет файлы только в подкаталогах, а также использование rsync, который, как я считаю, работает лучше, чем использование cp.

Дайте знать, если у вас возникнут вопросы.

Я думаю, что я бы исследовал решение в этом направлении:

while IFS= read -r -d '' filepath; do 
    basefilename="$( basename "${filepath}" )" 
    rsync -aiSP /source/"${filepath}" /destination/"${basefilename}" 
done < <( find ./*/ -type f -iname "*.*" -print0 ) 

Этот код более вероятно будет работать в текущем виде. Удачи!

Ответ или решение

Ваша задача заключается в том, чтобы скопировать все файлы из существующих поддиректорий в новые директории с теми же именами, что и исходные директории, исключая любые поддиректории. Это может быть достигнуто с помощью скриптов на Bash, и этот процесс можно автоматизировать с использованием утилиты find и циклов.

Теория

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

Пример

Рассмотрим следующую структуру:

$ source tree
├── sub1
│   └── sub1.1
│       ├── file1
│       └── file2
├── sub2
    └── sub2.1
        └── sub2.1.1
            ├── file1
            └── file2

Результатом выполнения скрипта должна стать такая структура:

├── sub1
│     ├── file1
│     └── file2
├── sub2
      ├── file1
      └── file2

Применение

Для реализации данной задачи используем следующий скрипт на Bash:

#!/bin/bash

srce="/path/to/source/directory"
trgt="/path/to/target/directory"

# Создаем целевую директорию, если она не существует
mkdir -p "$trgt"

# Перебор всех директорий первого уровня в исходной директории
for dir in "$srce"/*; do
    if [ -d "$dir" ]; then
        # Определяем имя текущей директории
        dirname=$(basename "$dir")
        # Создаем аналогичную директорию в целевой структуре
        mkdir -p "$trgt/$dirname"

        # Копируем все файлы из поддиректорий в корень текущей директории
        find "$dir" -type f -exec cp {} "$trgt/$dirname/" \;
    fi
done

Пояснения

  1. Создание целевой директории: mkdir -p "$trgt" создает целевую директорию, если она еще не существует, избегая ошибок.
  2. Перебор директорий первого уровня: for dir in "$srce"/*; просматривает все элементы в исходной директории и проверяет, являются ли они директориями.
  3. Копирование файлов: Используем find для поиска всех файлов в поддиректории и затем используем cp для копирования их в соответствующую целевую директорию без поддиректорий.

Этот скрипт надежно и эффективно решает поставленную задачу, оставляя после себя чистую и упорядоченную структуру каталогов.

Оцените материал
Добавить комментарий

Капча загружается...