Сглаживание вложенного каталога

71

Это, наверное, очень просто, но я не могу понять это. У меня есть такая структура каталогов (dir2 находится внутри dir1):

/dir1
    /dir2 
       |
        --- file1
       |
        --- file2

Каков наилучший способ «сгладить» эту структуру директора таким образом, чтобы получить file1 и file2 в dir1, а не в dir2.

черепаха
источник

Ответы:

75

Вы можете сделать это с GNU findи GNU mv:

find /dir1 -mindepth 2 -type f -exec mv -t /dir1 -i '{}' +

По сути, способ, который работает, если он findпроходит через все дерево каталогов и для каждого файла ( -type f), который не находится в каталоге верхнего уровня ( -mindepth 2), он запускает, mvчтобы переместить его в каталог, который вы хотите ( -exec mv … +). -tАргумент mvпозволяет указать каталог назначения первым, который необходим , потому что +форма -execпомещает все места источника в конце команды. В -iмарке mvспросить перед перезаписью дубликатов; Вы можете заменить, -fчтобы перезаписать их, не спрашивая (или -nне спрашивать или перезаписывать).

Как указывает Стефан Чазелас, вышеприведенное работает только с инструментами GNU (стандартными для Linux, но не для большинства других систем). Следующее несколько медленнее (потому что оно вызывается mvнесколько раз), но гораздо более универсально:

find /dir1 -mindepth 2 -type f -exec mv -i '{}' /dir1 ';'
derobert
источник
3
Отредактировано для использования, -exec +чтобы оно не выполняло большое количество процессовmv
Random832
1
@ Random832 И снова собираюсь вернуться, потому что + не работает. mvнужен пункт назначения в качестве последнего аргумента, но + будет иметь источники в качестве последнего аргумента. Find даже не примет синтаксис, который вы изменили на ( find: missing argument to `-exec')
derobert
1
@ Random832, но я полагаю mv, что -tмы можем использовать его, так что я его поменяю .
Дероберт
1
@Dom findпо умолчанию печатает скрытые (точечные) файлы. Глубина относительно каталога, который вы передаете, чтобы найти.
Дероберт
1
Или find ./dir -mindepth 2 -type f -exec mv -f '{}' ./dir ';'если переопределение дубликатов
Atav32
33

В зш:

mv dir1/*/**/*(.D) dir1

**/рекурсивно пересекает подкаталоги. Глоба отборочные . матчи обычных файлов только, и Dгарантирует , что точка файлы включены (по умолчанию, файлы, имя которых начинается с .исключены из шаблона соответствует). Чтобы потом очистить пустые каталоги, запустите rmdir dir1/**/*(/Dod)- /ограничивает каталоги и odсначала упорядочивает глубину совпадений, чтобы удалить их dir1/dir2/dir3раньше dir1/dir2.

Если общая длина имен файлов очень велика, вы можете столкнуться с ограничением длины командной строки. Zsh имеет встроенные команды для mvи rmdirкоторые не страдают от этого ограничения: бежать , zmodload zsh/filesчтобы включить их.

Только с инструментами POSIX:

find dir1 -type f -exec mv {} dir1 \;
find dir1 -depth -exec rmdir {} \;

или (быстрее, потому что не нужно запускать отдельный процесс для каждого файла)

find dir1 -type f -exec sh -c 'mv "$@" dir1' _ {} +
find dir1 -depth -exec rmdir {} +
Жиль "ТАК - перестань быть злым"
источник
1
Это должен быть принятый ответ! Особенно с краткой версией Zsh.
Адамски
3

Попробуйте сделать это:

cp /dir1/dir2/file{1,2} /another/place

или для каждого файла, соответствующего file[0-9]*в subdir:

cp /dir1/dir2/file[0-9]* /another/place

Смотрите http://mywiki.wooledge.org/glob

Жиль Квено
источник
Я должен был указать это, но мне нужно много файлов, чтобы использовать {}в моей реальной проблеме.
черепаха
Смотрите мое второе решение
Gilles Quenot
Бинго. Спасибо за помощь. Это определенно лучшее решение.
черепаха
2

Я написал две функции, которые вы можете использовать вместе, для этого вы можете ограничить уровень каталога, добавив -maxdepth $VALпараметр.

# This scripts flattens the file directory
# Run this script with a folder as parameter:
# $ path/to/script path/to/folder

#!/bin/bash

rmEmptyDirs(){
    local DIR="$1"
    for dir in "$DIR"/*/
    do
        [ -d "${dir}" ] || continue # if not a directory, skip
        dir=${dir%*/}
        if [ "$(ls -A "$dir")" ]; then
            rmEmptyDirs "$dir"
        else
            rmdir "$dir"
        fi
    done
    if [ "$(ls -A "$DIR")" ]; then
        rmEmptyDirs "$DIR"
    fi
}

flattenDir(){
    local DIR="$1"
    find "$DIR" -mindepth 2 -type f -exec mv -i '{}' "$DIR" ';'
}

read -p "Do you wish to flatten folder: ${1}? " -n 1 -r
echo    # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
    flattenDir "$1" &
    rmEmptyDirs "$1" &
    echo "Done";
fi
Bruno
источник
Чувак, я просто неправильно использовал твой сценарий, забыв аргумент пути, который просто испортил мой сервер. Хорошо, я парень, который копирует, вставляет вещи и злоупотребляет ими, но, ребята, будьте мудрыми и добавляйте проверки / подтверждения в сценарии, которые удаляют / перемещают подобные
вещи
Упс! Мне жаль это слышать. Надеюсь, у вас есть резервная копия ... Я добавил подтверждение для будущей защиты.
Бруно
Спасибо @Bruno, так намного лучше. Мой сервер по-прежнему работает без сбоев, я прокомментировал часть "flatten", чтобы просто рекурсивно удалять пустые каталоги из (и это была моя ошибка) root, пока не увидел ошибку, из-за которой я перестал запускать скрипт.
Дулган
1

Расширяя популярный ответ на этот вопрос, поскольку у меня был сценарий использования для выравнивания каталога, содержащего файлы с тем же именем.

dir1/
├── dir2
   └── file
└── dir3
    └── file

В этом случае переданная опция -i( --interactive) mvне даст желаемого результата, чтобы сгладить структуру каталогов и обработать конфликты имен. Так что это просто заменено на --backup=t(эквивалентно --backup=numbered). Дополнительная документация по опции -b( --backup) доступна по адресу https://www.gnu.org/software/coreutils/manual/coreutils.html#Backup-options .

В результате чего:

find dir1/ -mindepth 2 -type f -exec mv -t dir1/ --backup=t '{}' +

Который дает:

dir1/
├── dir2
├── dir3
├── file
└── file.~1~
Ян Евс
источник
1

tar и zip оба имеют возможность включать, а затем удалять структуру каталогов, поэтому я смог быстро сгладить вложенный каталог с помощью

tar -cvf all.tar *

с последующим перемещением all.tar в новое место, затем

tar -xvf all.tar --strip=4

Джон
источник