Сохранять структуру каталогов при перемещении файлов с помощью find

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

Я создал следующий скрипт, который перемещает старые файлы из заданного исходного каталога в целевой каталог. Он работает прекрасно.

#!/bin/bash

echo "Введите ваш исходный каталог"
read soure

echo "Введите ваш целевой каталог"
read destination 

echo "Введите количество дней"
read days

 find "$soure" -type f -mtime "-$days" -exec mv {} "$destination" \;

  echo "Файлы, которые были старше $days дней, перемещены из $soure в $destination"

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

С примером

/home/ketan     : исходный каталог

/home/ketan/hex : подпапка исходного каталога

/home/maxi      : целевой каталог

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

Я знаю, что find был указан, но это похоже на задачу для rsync.

Примеры

Зеркалировать файлы с той же структурой каталогов (исходный остается нетронутым):

rsync -axuv --progress Source/ Target/

Переместить файлы с той же структурой каталогов (удаляя из источника и очищая пустые каталоги):

rsync -axuv --prune-empty-dirs --remove-source-files --progress Source/ Target/

Переместить файлы определенного типа (пример):

rsync -rv --include '*/' --include '*.js' --exclude '*' --prune-empty-dirs Source/ Target/

Переместить файлы, полученные в результате расширенного поиска find:

cd "$source" &&
  rsync -av --remove-source-files --prune-empty-dirs --progress --files-from <(find . -type f -mtime -$days) . "$destination"

Примечание о удалении файлов

Существуют параметры для rsync, которые могут гарантировать, что ваш целевой каталог зеркалирует ваш исходный каталог, что затем удаляет файлы в целевом каталоге, которые уже не находятся в вашем источнике (т.е. --delete-before, --delete-after, --delete-during, и --delete-delay.

Вы также можете разрешить удаление файлов из исходного каталога, когда они были перемещены из целевого каталога, т.е. --remove-source-files.

Это зависит от случая использования, так что как вы это реализуете – решать вам.

Примечание: Как указывает Шридар Сарнобат, если вы rsync каталог с символическими ссылками на каталог, в который вы rsync’ите, и передаете –remove-source-files`, вы можете столкнуться с потерей данных.

Вместо того, чтобы выполнять mv /home/ketan/hex/foo /home/maxi, вам нужно варьировать целевой каталог на основе пути, полученного с помощью find. Это проще, если вы сначала перейдете в исходный каталог и выполните find .. Теперь вы можете просто добавить целевой каталог перед каждым элементом, полученным с помощью find. Вам нужно будет запустить оболочку в команде find … -exec, чтобы выполнить конкатенацию и создать целевой каталог, если это необходимо.

destination=$(cd -- "$destination" && pwd) # сделать его абсолютным путем
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  mkdir -p "$0/${1%/*}"
  mv "$1" "$0/$1"
' "$destination" {} \;

Обратите внимание на то, что чтобы избежать проблем с кавычками, если $destination содержит специальные символы, вы не можете просто заменить его внутри скрипта оболочки. Вы можете экспортировать это в окружение, чтобы оно достигло внутренней оболочки, или вы можете передать это в качестве аргумента (это то, что я сделал). Вы можете немного сэкономить время выполнения, сгруппировав вызовы sh:

destination=$(cd -- "$destination" && pwd) # сделать его абсолютным путем
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  for x do
    mkdir -p "$0/${x%/*}"
    mv "$x" "$0/$x"
  done
' "$destination" {} +

В качестве альтернативы, в zsh, вы можете использовать zmv функцию, и . и m глобальные квалификаторы, чтобы совпадать только с обычными файлами в правильном диапазоне дат. Вам нужно будет передать альтернативную функцию mv, которая сначала создает целевой каталог, если это необходимо.

autoload -U zmv
mkdir_mv () {
  mkdir -p -- $3:h
  mv -- $2 $3
}
zmv -Qw -p mkdir_mv $source/'**/*(.m-'$days')' '$destination/$1$2'

Вы можете сделать это, используя два экземпляра find(1)

Всегда можно использовать cpio(1)

(cd "$soure" && find … | cpio -pdVmu "$destination")

Проверьте аргументы для cpio. Те, которые я дал

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

find /original/file/path/* -mtime +7 -exec cp {} /new/file/path/ \;
find /original/file/path/* -mtime +7 -exec rm -rf {} \;

Обратите внимание: недостаток, обнаруженный @MV для автоматизированных операций:

Использование двух отдельных операций рискованно. Если некоторые файлы становятся старше 7 дней, пока операция копирования выполняется, они не будут скопированы, но будут удалены операцией удаления. Для чего-то, что выполняется вручную раз, это может быть не проблемой, но для автоматизированных скриптов это может привести к потере данных.

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

find "$soure" -type f -mtime "-$days" -print0 | xargs -0 -I {} sh -c '
    file="{}"
    destination="'"$destination"'"
    mkdir -p "$destination/${file%/*}"
    mv "$file" "$destination/$file"'

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

export destination
find "$soure" -type f "-$days" -print0 | xargs -0 -n 10 bash -c '
for file in "$@"; do
  echo -n "Перемещение $file в $destination/"`dirname "$file"`" ... "
  mkdir -p "$destination"/`dirname "$file"`
  \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " ошибка !" && echo "завершено."
done'

Или быстрее, перемещая кучу файлов одновременно для многоядерных процессоров, используя команду “parallel”:

echo "Перемещение самых старых $days файлов из $soure в $destination параллельно (по 10 файлов на "`parallel --number-of-cores`" задач):"
function move_files {
  for file in "$@"; do
    echo -n "Перемещение $file в $destination/"`dirname "$file"`" ... "
    mkdir -p "$destination"/`dirname "$file"`
    \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " ошибка !" && echo "завершено."
  done
}
export -f move_files
export destination
find "$soure" -type f "-$days" -print0 | parallel -0 -n 10 move_files

П.С.: У вас опечатка, “soure” должно быть “source”. Я сохранил название переменной.

Это менее элегантно, но легко, если количество / размер файлов не слишком велико.

Сожмите ваши файлы в архив zip, а затем распакуйте в целевом каталоге без опции -j. По умолчанию zip создаст относительную структуру каталогов.

Я использую это таким образом

cp -r source/ destination/
find destination/ -not -path "*/mypattern/*.py" -delete

В основном, копирую все из источника в назначение и удаляю все кроме необходимого.

Вот что я использую, с find, tar и rm. Замените аргументы find на те, которые вам нужны, но сохраните опцию -type f, только для файлов.

cd srcdir        # ИЛИ pushd srcdir

find . -mtime +7 -type f |  while read fn; do echo Перемещение $fn; tar cf - "$fn" | ( cd destdir; tar xf - );  rm -f "$fn"; done

# popd 

(Пропустите команду “echo Перемещение $fn”, чтобы выполнить задачу без лишних слов.)
Имеющийся метод может оставить пустые каталоги в дереве исходников. Вы можете использовать

find srcdir -empty -type d -delete 

для удаления пустых каталогов.

Вы можете попробовать с cp --parents, сначала переключившись на этот каталог, и добавив --delete в конце, чтобы имитировать перемещение:

 cd "$soure"
 find "$soure" -type f -mtime "-$days" -exec cp --parents {} "$destination" \; -delete
 cd -

если вы хотите увидеть копируемые файлы и не обновлять уже существующие файлы, тогда вы можете использовать:

 cd "$soure"
 find "$soure" -type f -mtime "-$days" -print -exec cp --update=none --parents {} "$destination" \; -delete
 cd -

Это немного медленнее, чем перемещение файлов, но создает необходимую структуру каталогов, когда это необходимо.

Основываясь на полезном ответе Жиля выше, который я не совсем понял, вот рабочий пример скрипта, который перемещает (в той же файловой системе) кучу очень больших файлов из текущего каталога . в другую папку ../DDD20-orig.

Перемещаются только файлы, соответствующие *.aedat или *.hdf5

#!/bin/bash

source=.
destination=../DDD20-orig
dryrun="echo" # измените на "" для реального запуска
# dryrun="" # раскомментируйте для реального запуска

pushd $destination
destination=$(pwd) # сделать его абсолютным путем
echo "Целевой каталог: $destination"
popd
find . -name '*.aedat' -print0 -o -name '*.hdf5' -print0 | while read -d $'\0' x; do # поместите любой файл в $x
    dir="$x/${x%/*}" # возьмите полный путь к файлу ($x) и удалите часть с именем файла и расширением в конце
    destdir="$destination/$dir"
    echo "Файл: $x, целевой каталог: $destdir"
    $dryrun mkdir -p "$destdir"
    $dryrun mv "$x" "$destdir"
done

Чтобы выполнить тестовый запуск, установите dryrun в echo.

Это выполнено менее чем за 1 секунду, по сравнению с часами, которые потребовались бы для копирования терабайта файлов.

часть вывода тестового запуска:

tobi.delbruck@sensors-nas:/share/datasets/resiliosync/DDD20$ ./move-orig.sh                                                                                                                              
/share/datasets/resiliosync/DDD20-orig /share/datasets/resiliosync/DDD20
/share/datasets/resiliosync/DDD20
mkdir -p /share/datasets/resiliosync/DDD20-orig/./DDD20_Ford_Focus/fordfocus/aug01
mv ./DDD20_Ford_Focus/fordfocus/aug01/rec1501651162.hdf5 /share/datasets/resiliosync/DDD20-orig/./DDD20_Ford_Focus/fordfocus/aug01/rec1501651162.hdf5
mkdir -p /share/datasets/resiliosync/DDD20-orig/./DDD20_Ford_Focus/fordfocus/aug01
mv ./DDD20_Ford_Focus/fordfocus/aug01/rec1501650719.hdf5 /share/datasets/resiliosync/DDD20-orig/./DDD20_Ford_Focus/fordfocus/aug01/rec1501650719.hdf5

Попробуйте так:

IFS=$'\n'
for f in `find "$soure" -type f -mtime "-$days"`;
do
  mkdir -p "$destination"/`dirname $f`;
  mv $f "$destination"/`dirname $f`;
done

Поскольку, похоже, нет действительно простого решения для этого, и это мне нужно очень часто, я создал этот утилиту с открытым исходным кодом для linux (требуется python): https://github.com/benapetr/smv

Существует множество способов, как вы могли бы использовать ее, чтобы достичь того, что вам нужно, но, возможно, самым простым будет что-то вроде этого:

 # -vf = подробный + принудительный (не останавливается на ошибках)
smv -vf `find some_folder -type f -mtime "-$days"` target_folder

Вы также можете запустить его в тестовом режиме, чтобы он ничего не делал, кроме как печатал, что он бы сделал.

smv -rvf `find some_folder -type f -mtime "-$days"` target_folder

Или, если список файлов слишком длинный, чтобы уместиться в аргументе строки, и вам не мешает выполнять python для каждого отдельного файла, тогда

find "$soure" -type f -mtime "-$days" -exec smv {} "$destination" \;

#!/bin/bash

# '+' здесь означает, что 'find' ищет файлы старше 45 дней сТеперь
# может быть заменено на '-' (-45), чтобы найти файлы с датой изменения с Теперь до 45 дней (макс)
days="+45"

source=/var/log

destination=/root/logsbackups

# поиск может занять некоторое время, поэтому информируйте пользователя/скрипт, что происходит
echo "\nПоиск файлов..."

# сбор списка файлов как массива в переменной
LIST_OF_FILES=(`find $source -type f -mtime $days`)

echo "Перемещение файлов..."

# обработка собранных файлов в массиве для правильного перемещения
for file in ${LIST_OF_FILES[@]}; do

  # реальный полный путь к файлу (без имени файла)
  filepath=$(dirname $file)

  # полное имя файла (без пути)
  filename=$(basename $file)

  # информационный лог (необязательный)
  echo "Перемещение в $destination$filepath$filename"

  # убедитесь, что целевой каталог существует
  mkdir -p $destination$filepath

  # перемещение файла
  mv -f $file $destination$filepath$filename

done

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

Сохранение структуры директорий при перемещении файлов с помощью команды find

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

Проблема

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

Решение

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

#!/bin/bash

echo "Введите ваш исходный каталог"
read source

echo "Введите ваш целевой каталог"
read destination 

echo "Введите количество дней"
read days

# Приводим путь к целевому каталогу к абсолютному пути
destination=$(cd -- "$destination" && pwd) 

# Переходим в исходный каталог
cd -- "$source" && 

# Используем find для поиска файлов
find . -type f -mtime "-$days" -exec sh -c '
  # Для каждого найденного файла
  for filepath do
    # Создаём нужную директорию в целевом каталоге
    mkdir -p "$0/${filepath%/*}"

    # Перемещаем файл в соответствующую директорию
    mv "$filepath" "$0/$filepath"
  done
' "$destination" {} +

Пояснения к скрипту

  1. Ввод пользователя: Скрипт запрашивает у пользователя исходный и целевой каталоги, а также количество дней, за которые нужно переместить файлы.

  2. Получение абсолютного пути: Поскольку работа с относительными путями может привести к путанице, мы используем конструкцию cd для определения абсолютного пути к целевому каталогу.

  3. Поиск файлов: Используем команду find, чтобы получить список файлов, которые были изменены в последние days дней.

  4. Шелл-скрипт: Внутренний скрипт, запускаемый для каждого найденного файла:

    • Команда mkdir -p создаёт необходимые подкаталоги в целевом каталоге, если они не существуют.
    • Затем файл перемещается в соответствующий каталог с помощью команды mv.

Пример использования

Предположим, что у вас есть следующий исходный каталог:

/home/ketan
├── file1.txt
└── hex
    └── file2.txt

Если вы хотите переместить файлы, измененные за последние 7 дней, в каталог /home/maxi, после запуска скрипта структура в целевом каталоге будет выглядеть следующим образом:

/home/maxi
└── hex
    └── file2.txt

Заключение

Этот подход позволяет эффективно перемещать файлы с сохранением структуры каталогов, минимизируя риск потери данных. Скрипт легко адаптировать под ваши нужды, изменяя условия поиска, пути и другие параметры. Используя команду find вместе с mkdir и mv, вы можете красиво автоматизировать процесс организации файлов в вашей системе.

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

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