Вопрос или проблема
Я создал следующий скрипт, который перемещает старые файлы из заданного исходного каталога в целевой каталог. Он работает прекрасно.
#!/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" {} +
Пояснения к скрипту
-
Ввод пользователя: Скрипт запрашивает у пользователя исходный и целевой каталоги, а также количество дней, за которые нужно переместить файлы.
-
Получение абсолютного пути: Поскольку работа с относительными путями может привести к путанице, мы используем конструкцию
cd
для определения абсолютного пути к целевому каталогу. -
Поиск файлов: Используем команду
find
, чтобы получить список файлов, которые были изменены в последниеdays
дней. -
Шелл-скрипт: Внутренний скрипт, запускаемый для каждого найденного файла:
- Команда
mkdir -p
создаёт необходимые подкаталоги в целевом каталоге, если они не существуют. - Затем файл перемещается в соответствующий каталог с помощью команды
mv
.
- Команда
Пример использования
Предположим, что у вас есть следующий исходный каталог:
/home/ketan
├── file1.txt
└── hex
└── file2.txt
Если вы хотите переместить файлы, измененные за последние 7 дней, в каталог /home/maxi
, после запуска скрипта структура в целевом каталоге будет выглядеть следующим образом:
/home/maxi
└── hex
└── file2.txt
Заключение
Этот подход позволяет эффективно перемещать файлы с сохранением структуры каталогов, минимизируя риск потери данных. Скрипт легко адаптировать под ваши нужды, изменяя условия поиска, пути и другие параметры. Используя команду find
вместе с mkdir
и mv
, вы можете красиво автоматизировать процесс организации файлов в вашей системе.