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


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

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

Ответы:


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 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. Опять же, некоторые оболочки Bourne вели себя по-разному, но 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

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

Там всегда cpio (1)

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

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


1
Это сломает любые имена файлов с пробелами.
Крис Даун

1
Это копирует файлы, а не перемещает их.
Жиль "ТАК - перестань быть злым"

Возможно, это не идеальный ответ, но он помог мне переместить файлы с сохранением путей более или менее прямым способом (у меня достаточно места, чтобы скопировать файлы, а затем удалить). Проголосовал
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.