Вопрос или проблема
Я постараюсь объяснить это, насколько смогу. У меня есть несколько сотен директорий, каждая из которых содержит несколько других поддиректорий и файлы, разбросанные по этим поддиректориям. Мне не нужны и не нужны поддиректории, только файлы, но названия директорий должны остаться прежними. Таким образом, я хотел бы перейти от:
$ исходная структура
├── 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
Пояснения
- Создание целевой директории:
mkdir -p "$trgt"
создает целевую директорию, если она еще не существует, избегая ошибок. - Перебор директорий первого уровня:
for dir in "$srce"/*;
просматривает все элементы в исходной директории и проверяет, являются ли они директориями. - Копирование файлов: Используем
find
для поиска всех файлов в поддиректории и затем используемcp
для копирования их в соответствующую целевую директорию без поддиректорий.
Этот скрипт надежно и эффективно решает поставленную задачу, оставляя после себя чистую и упорядоченную структуру каталогов.