Как перезаписать целевые файлы с помощью mv?

155

У меня есть тонна файлов и каталогов в подкаталоге, который я хочу переместить в родительский каталог. В целевом каталоге уже есть несколько файлов и каталогов, которые необходимо перезаписать. Файлы, которые присутствуют только в цели, должны быть оставлены нетронутыми. Могу ли я заставить mvэто сделать? ( mv * ..) Жалуется

mv: cannot move `xyz' to `../xyz': Directory not empty

Что мне не хватает?

EricSchaefer
источник
3
Ты пробовал mv -f?
Сакиск
Интересно, почему mv -fне правильный ответ.
Педро Лобито
@PedroLobito: Потому что это не работает? -f только перезаписывает файлы, но не работает, если вы перемещаете подкаталоги, которые существуют в месте назначения и не являются пустыми.
EricSchaefer

Ответы:

118

Вам нужно будет скопировать их в место назначения, а затем удалить источник, используя команды, cp -r * ..сопровождаемые rm -rf *.

Я не думаю, что вы можете "объединить" каталоги, используя mv.

кендырь
источник
4
Ну, это то, что я не хотел делать, потому что это займет много времени ... В любом случае, спасибо.
EricSchaefer
12
Предположительно mvбыстрее, потому что вы находитесь на той же файловой системе? Что если вы используете cp -lдля создания жестких ссылок, а не для перемещения файлов?
Mattdm
6
Вы должны использовать cp -aвместо cp -rтого, чтобы сохранить атрибуты файла (метка времени, разрешения и т. Д.).
dotancohen
3
Для людей, которые приходят сюда поздно через Google, ответ @palswim ниже имитирует поведение mv, создавая новые жесткие ссылки на данные, а затем удаляя старые ссылки. Короткий ответ cp -rl source destination && rm -r source.
Уильям Эверетт
72

rsyncвероятно, будет лучшим вариантом здесь. Это так же просто, как rsync -a subdir/ ./.

Мое тестовое дерево в filename: contentsformat:

./file1:root
./file2:root
./dir/file3:dir
./dir/file4:dir
./subdir/dir/file3:subdir
./subdir/file1:subdir

Бег rsync:

$ rsync -a -v subdir/ ./
sending incremental file list
./
file1
dir/
dir/file3

дает:

./file1:subdir
./file2:root
./dir/file3:subdir
./dir/file4:dir
./subdir/dir/file3:subdir
./subdir/file1:subdir

И затем, чтобы эмулировать mv, вы, вероятно, хотите удалить исходный каталог:

$ rm -r subdir/

Предоставление:

./file1:subdir
./file2:parent
./dir/file3:subdir
./dir/file4:dir

Если это не так, можете ли вы привести аналогичный пример (например, используя мое тестовое дерево в верхней части этого ответа) с желаемым результатом?

Mikel
источник
1
Rsync копирует. Этот вопрос о переезде.
Жиль
1
@ Жиль: Спасибо. Я добавил rm -rв конце, чтобы сделать его в основном так же, как mv.
Микель
3
copy-then-delete не эквивалентно mv, когда источник и место назначения находятся в одной файловой системе. mvявляется атомарным, сохраняет номера инодов (так что файл может оставаться открытым) и не требует времени и пространства при создании копии.
Жиль
5
@ Жиль: Я понимаю, что в настоящее время ведущий ответ cp -r; rm -r. Я думаю, что в этом смысле rsyncстоит упомянуть тоже.
Микель
Я уже сделал это с помощью cp / rm (это было срочно). Это действительно заняло много времени. Сценарий Жиля, вероятно, был бы намного быстрее, но он тоже опоздал.
EricSchaefer
48

rsyncМожно удалить источник после копирования с --remove-source-filesпараметром. Это должен быть удобный способ сделать то, что вы хотите.

Из rsync man page:

        --remove-source-files   sender removes synchronized files (non-dir)
Майк Чен
источник
Это действительно лучший ответ. Я не мог использовать cp -r; rmиз-за недостатка свободного места. Вместо этого rsync --remove-source-filesоба минимизировали используемое дисковое пространство, избегая копировать одни и те же файлы.
гуака
20

Вы можете сделать это с помощью cpи rm, но без копирования огромного количества данных, которые вы (предположительно) пытаетесь избежать передачи. @mattdm упомянул об этом в своем комментарии , и ответ на другой вопрос содержит более полное обсуждение различных вариантов.

cp -rlf source destination
rm -r source

По сути, -lопция для cpкоманды создает жесткие ссылки на файлы, а не копирует их данные в новые файлы.

palswim
источник
1
Я знаю, что ОП уже выполнил свою задачу, которая поставила вопрос, но, надеюсь, этот ответ может помочь любому с этой проблемой в будущем.
Palswim
Это действительно ответ, который им нужен. Я только что сделал это с 60 ГБ тысяч небольших файлов электронной почты Cyrus, и это заняло всего 21 секунду.
Лабрадорт
Я также выбрал этот путь - я просто изменил его, cp -al source destinationчтобы сохранить информацию о владельце и разрешения.
piit79
Я думаю, что для того, чтобы сделать то, о чем просил OP, нужно добавить опцию -f, иначе файл не будет перезаписан, если файл существует. Вы можете это подтвердить или я что-то не так делаю?
дас Кекс
@dasKeks: на самом деле cpперезаписывает по умолчанию. -fОпция пытается удалить файл (как в rm) , прежде чем пытаться скопировать заново, что может помочь , если процесс не может открыть файл для записи, хотя некоторые люди предпочли бы видеть , что произошла ошибка , вместо этого. Я не знаю, имело ли это значение для ОП, но я -fвсе равно добавил флаг к своему ответу.
Palswim
8

Вот скрипт, который перемещает файлы из-под /path/to/source/rootсоответствующего пути в /path/to/destination/root.

  • Если каталог существует как в источнике, так и в месте назначения, содержимое рекурсивно перемещается и объединяется.
  • Если файл или каталог существует в источнике, но не в месте назначения, он перемещается.
  • Любой файл или каталог, который уже существует в месте назначения, остается позади. (В частности, объединенные каталоги остаются в источнике. Это не легко исправить.)

Осторожно, непроверенный код.

export dest='/path/to/destination/root'
cd /path/to/source/root
find . -type d \( -exec sh -c '[ -d "$dest/$0" ]' \; -o \
                  -exec sh -c 'mv "$0" "$dest/$0"' {} \; -prune \) \
    -o -exec sh -c '
        if ! [ -e "$dest/$0" ]; then
          mv -f "$0" "$dest/$0"
        fi
' {} \;
жилль
источник
Два исправления: вам нужно \;перед -oпервой строкой findкоманды, и вы не должны убегать !в if- это просто !, а не\!
llhuii
2

Эта тема существует уже много лет и по-прежнему занимает первое место в Google, поэтому я хотел добавить еще один метод. Как я обычно это делаю: упаковываю содержимое subdir в tarball, перемещаем tarball в родительский каталог и затем извлекаю его с поведением по умолчанию --overwrite Это именно то, что вы ищете. После этого вы можете удалить свой subdir.

cd xyz
tar -cvzpf tmp.tar.gz *
mv tmp.tar.gz ../tmp.tar.gz
cd ..
tar -xvzpf tmp.tar.gz
rm -rf xyz
rm -f tmp.tar.gz
Саймон Краус
источник
Это работает, только если у вас есть дополнительное место для сжатого файла tar. И вы действительно хотите сохранить временный tar-файл после извлечения?
Anthon
Отредактированный код для удаления файла TMP. В настоящее время пространство не является проблемой в большинстве случаев. #terabyteages
Саймон Краус
0

Если у вас достаточно памяти, вы можете сделать это следующим образом:

mv -bfv directory_1/* directory_2/ # all duplicate source files/directories 
                                   # will have ~ appended to them
find -name "*~" -delete            # will recursively find and delete all files 
                                   # with ~ on the end

Убедитесь, что нет важных файлов с ~ в конце, но если они есть, вы можете добавить их --suffix=whateveryouwantвместо стандартных.

4D3H2
источник