Мне нужен способ поиска в каталогах дочерних каталогов с тем же именем, а затем перемещения всех файлов из дочернего каталога в родительский. Таким образом,
/recup-dir1/recup-dir1/files to /recup-dir1/files.
дочерние каталоги можно оставить пустыми, потому что я могу использовать что-то вроде
find . -type -d -empty -delete
удаления всех пустых каталогов
Проблема в том, что я понятия не имею, в каких каталогах есть дочерние каталоги с таким же именем, а в каких — нет.
В псевдокоде мне нужно что-то вроде этого.
While more directories are unchecked
get name-x of next dir
enter dir
If name-x/name-x exist
move all files in name-x/name-x to name-x
mark dir as done
next
Лучшее, что я предлагаю, — это создать небольшой скрипт на Python, чтобы составить список всех каталогов, имеющих дочерние элементы с таким же именем, и пропустить этот список через команду вроде
find something something -exec mv
Может быть, это можно сделать с помощью скриптов bash или есть другое решение. Например, какая-то команда rsync, однако, поскольку я создал этот беспорядок, вероятно, с помощью rsync, я не думаю, что это будет решением.
Редактировать: вот фактическая часть вывода дерева: Каталоги верхнего уровня находятся внутри /mnt/external-disk/tst-backup. На более низких уровнях нет подкаталогов.
│ └── recup_dir.1
├── recup_dir.10
│ └── recup_dir.10
├── recup_dir.100
│ └── recup_dir.100
├── recup_dir.102
│ └── recup_dir.102
└── recup_dir.1020
└── recup_dir.1020
решение1
С помощью zsh
вы можете сделать:
for dir in **/*(NDodoN/e['[[ $REPLY:t = $REPLY:h:t ]]']); do
contents=($dir/*(NDoN))
(( $#contents == 0 )) ||
mv -- $contents $dir:h/ &&
rmdir -- $dir
done
Где:
**/*(qualifiers)
рекурсивная подстановка с квалификаторами подстановкиN
: nullglob: не жалуйтесь, если нет совпаденийD
: dotglob: включить скрытые файлыod
: сначала сортировка по глубине (сначала листья, потом ветки, на которых они находятся).oN
: в противном случае не беспокойтесь об упорядочивании списка файлов./
: ограничить файлами типа каталог.e['expression']
: ограничиться файлами, для которыхexpression
код возвращает значение true (внутри которых хранится текущий путь к файлу$REPLY
).$REPLY:t
: хвост (базовое имя) файла$REPLY:h:t
: хвост заголовка (имя_каталога) файлов)
С bash
4.4+ и GNU find
или find
большинством BSD вы можете сделать что-то подобное с помощью:
shopt -s nullglob dotglob
readarray -td '' dirs < <(
LC_ALL=C find . -depth -regex '.*\(/[^/]*\)\1' -type d -print0
)
for d in "${dirs[@]}"; do
contents=("$d"/*)
(( ${#contents[@]} == 0 )) ||
mv -- "${contents[@]}" "${d%/*}/" &&
rmdir -- "$d"
done
На этот раз для сопоставления файлов используется регулярное выражение ./path/to/dir/dir
с использованием базовых обратных ссылок на регулярные выражения.
решение2
Попробуйте это, основанное на GNU find
v4.8.0 и Bash v5.1.8
Часть 1: Анализ дерева каталогов + обнаружение дубликатов имен подкаталогов
Предположим, что определенный каталог в вашем дереве имеет следующую структуру:
./
|__test1/
|__dirname with space
| |__test2
| |__ test2
|__dirname **
| |__test1
|
|__reboot
| |__test1
|
|__test2/
|__test3/
|__test2/
|__test1/
|__test1/
(Странные имена каталогов приведены для демонстрации безопасности кода.)
Вы видите, что некоторые подкаталоги (subdirs) повторяются по-разному. Некоторые повторяются несколько раз, а не один раз (например test1
), один не повторяется ( test3
), и они могут повторяться как родительские и дочерние или разделяться произвольным числом промежуточных подкаталогов.
Приведенный ниже код подробно раскрывает дубликаты имен подкаталогов в структуре каталогов.
- он анализирует файловое дерево на предмет структуры подкаталогов, начиная с
$PWD
- он находит дубликаты для каждого компонента любого пути подкаталога из 2 или более уровней, не считая корневого уровня, который является
$PWD
. В моем эксперименте самый длинный путь подкаталога:./test1/test2/test1/test3/test2/test1/test1
- он выводит первый дубликат подкаталога, найденный на каждом уровне подкаталога, начиная с листа, т.е. читая путь подкаталога справа налево.
- Печать перенаправляется в файл в обратном порядке, поэтому сначала отображается самый длинный путь подкаталога. Две последовательные точки с запятой разделяют компоненты пути (слева от ";;"), от первого дубликата (справа от ";;"), найденного в соответствии с предыдущим пунктом.
[Код]
$ find ./* -type d -exec bash -c 'set -o noglob; IFS="/" subdir=($(printf "%s " "$1")); dirlevels=$((${#subdir[@]}-1)); dupe="$(awk '\''!($1 in sd) {sd[$1];next} {print $1}'\'' < <(printf "%s\n" ${subdir[@]:1}))";[ $dirlevels -ge 2 ] && [ ! -z "$dupe" ] && (printf "%s/" "${subdir[@]:1}";printf " ;; %s\n" "$(tail -n 1 < <(printf "%s\n" "$dupe"))";)' shellexec {} \; | tac >| tmp.data
$ cat -n tmp.data
1 test1/reboot/test1/ ;; test1
2 test1/dirname with space/test2/test2/ ;; test2
3 test1/test2/test1/test3/test2/test1/test1/ ;; test1
4 test1/test2/test1/test3/test2/test1/ ;; test1
5 test1/test2/test1/test3/test2/ ;; test2
6 test1/test2/test1/test3/ ;; test1
7 test1/test2/test1/ ;; test1
8 test1/dirname **/test1/ ;; test1
Часть 2: Обработка дубликатов имен подкаталогов; перемещение содержимого
Обработка происходит в порядке, указанном в tmp.data
.
- в
tmp.data
первой строке первое имя dupe в пути./test1/test2/test1/test3/test2/test1/test1
—test1
. Мы можем перенести его содержимое в самый левый уровень подкаталога с тем же именем:./test1/
- После перемещения содержимого без затирания существующих файлов в месте назначения самый правый уровень подкаталога
test1
удаляется. - переходим к строке 2
tmp.data
и повторяем вышеуказанные шаги. - и т.д., пока все линии не
tmp.data
будут израсходованы.
На этом этапе вопрос (автору вопроса: @TomDerks) заключается в том, что делать с крайней правой частью test1/*
строки 6?всеего содержимое будет перемещено в самый левый каталог с тем же именем, который в данном случае является первым уровнем подкаталога в пути? Включает ли "все" файлы в./test1/test2/test1/
а такжеподкаталог test3
и его содержимое?
Полное решение (часть 2) зависит от этого.