Збережіть структуру каталогів під час переміщення файлів за допомогою find


22

Я створив наступний скрипт, який переміщує файли старих днів, як визначено з вихідного каталогу до каталогу призначення. Це працює чудово.

#!/bin/bash

echo "Enter Your Source Directory"
read soure

echo "Enter Your Destination Directory"
read destination 

echo "Enter Days"
read days



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

  echo "Files which were $days Days old moved from $soure to $destination"

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

з прикладом

/home/ketan     : source directory

/home/ketan/hex : source subdirectory

/home/maxi      : destination directory

Коли я запускаю цей скрипт, він також переміщує шістнадцяткові файли в каталог maxi, але мені потрібно, щоб той самий шістнадцятковий був створений у каталог maxi і переміщав його файли в той же шістнадцятковий.

Відповіді:


13

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

destination=$(cd -- "$destination" && pwd) # make it an absolute path
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) # make it an absolute path
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 glob лише для відповідності звичайним файлам у потрібному діапазоні дат. Вам потрібно буде пройти альтернативну mvфункцію, яка спочатку створює цільовий каталог, якщо це необхідно.

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

for x do, ти ;там пропав безвісти :). Крім того, я не маю уявлення, чого ви хотіли досягти, $0але я впевнений, що це буде sh:).
Michał Górny

@ MichałGórny for x; doтехнічно не відповідає POSIX (перевірити граматику ), але сучасні оболонки дозволяють і for x doі for x; do; деякі старі снаряди Борна не гаркали for x; do. На сучасних оболонках з sh -c '…' arg0 arg1 arg2 arg3, arg0стає $0, arg1стає $1і т. Д. Якщо ти хочеш $0бути sh, потрібно писати sh -c '…' sh arg1 arg2 arg3. Знову ж таки, деякі снаряди Борна поводилися інакше, але POSIX це вказує.
Жиль "ТАК - перестань бути злим"

pushdздається, кращий вибір, який cdяк би менш нав'язливий у поточному середовищі.
jpmc26

16

Я знаю, що findбуло визначено, але це звучить як робота для rsync.

Я найчастіше використовую таке:

rsync -axuv --delete-after --progress Source/ Target/

Ось хороший приклад, якщо ви хочете переміщувати лише файли певного типу файлу ( приклад ):

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

набагато чистіші за інші рішення :)
Гійом

2
Я виявив, що це --remove-source-filesбуло корисно, що ефективно призводить до переміщення файлів замість копіювання. Це дивовижне використання rsync.
Том

3

ви могли це зробити, використовуючи два екземпляри пошуку (1)

Завжди є cpio (1)

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

Перевірте аргументи щодо cpio. Ті, що я дав


1
Це порушить будь-які назви файлів з пробілом.
Кріс Даун

1
Це копіює файли замість їх переміщення.
Жил "ТАК - перестань бути злим"

Можливо, це не є ідеальною відповіддю, але це допомогло мені перемістити файли, що зберігають шляхи, більш-менш прямим способом вперед (у мене достатньо місця для копіювання файлів, а потім видалення). Upvoted
AhHatem

3

Це не так ефективно, але, на мій погляд, код легше читати і розуміти, якщо ви просто скопіюєте файли і потім видалите.

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

Примітка. Проблема, виявлена ​​@MV для автоматизованих операцій:

Використання двох окремих операцій ризиковано. Якщо деякі файли старіють за 7 днів під час операції копіювання, вони не будуть скопійовані, але вони будуть видалені операцією видалення. Якщо щось робиться вручну один раз, це може не бути проблемою, але для автоматизованих сценаріїв це може призвести до втрати даних


2
Використання двох окремих операцій ризиковано. Якщо деякі файли стануть старше 7 днів під час операції копіювання, вони не будуть скопійовані, але вони будуть видалені операцією видалення. Якщо щось робиться вручну один раз, це може не бути проблемою, але для автоматизованих сценаріїв це може призвести до втрати даних.
МВ.

2
Просте рішення цього недоліку - запустити пошук один раз, зберегти список як текстовий файл, а потім два рази використовувати xargs, щоб зробити копію, а потім видалити.
Девід М. Перлман

1

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

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

Зараз Кріс пересуває весь дім на максі
Кетан Пател

@ K.KPatel Ні, це не так. Він просто зберігає структуру вашого каталогу.
Кріс Даун

Це розривається, якщо $destinationмістить спеціальні символи, оскільки воно зазнає розширення у внутрішній оболонці. Може, ти мав на увазі destination='\'"$destination"\''? Це все ще ламається '. Також це створює файли, такі як /home/maxi/home/ketan/hex/fooзамість /home/maxi/hex/foo.
Жил "ТАК - перестань бути злим"

0

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

export destination
find "$soure" -type f "-$days" -print0 | xargs -0 -n 10 bash -c '
for file in "$@"; do
  echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
  mkdir -p "$destination"/`dirname "$file"`
  \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
done'

Або швидше, переміщаючи купу файлів за один і той же час для декількох процесорів, використовуючи команду "паралельний":

echo "Move oldest $days files from $soure to $destination in parallel (each 10 files by "`parallel --number-of-cores`" jobs):"
function move_files {
  for file in "$@"; do
    echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
    mkdir -p "$destination"/`dirname "$file"`
    \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
  done
}
export -f move_files
export destination
find "$soure" -type f "-$days" -print0 | parallel -0 -n 10 move_files

PS: У вас помилка друку, "soure" має бути "джерелом". Я зберегла ім'я змінної.


0

Це менш елегантно, але легко, якщо кількість / розмір файлів не надто великий

Збирайте файли разом в zipархів, а потім розпакуйте їх за призначенням без -jможливості. За замовчуванням zip створить відносну структуру каталогів.


0

Спробуйте таким чином:

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

0

Оскільки, здається, немає справді простого рішення для цього, і мені це потрібно дуже часто, я створив цю утиліту з відкритим кодом для Linux (вимагає python): https://github.com/benapetr/smv

Існує кілька способів, як ви могли використовувати його для досягнення того, що вам потрібно, але, мабуть, самим простим було б щось подібне:

 # -vf = verbose + force (doesn't stop on errors)
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" \;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.