Переместить или скопировать без перезаписи и проверить успешность.

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

Проблема:
Я ищу способ переименовать или скопировать файл, не перезаписывая файл назначения, если он существует, а затем проверить успех операции перемещения или копирования. Я ищу метод, который будет работать с версиями mv/cp BSD, установленными на MacOS/Unix, а также с версиями GNU coreutils, которые у меня есть на Linux.

Попытка решения:
Во всех версиях mv/cp я могу предотвратить перезапись файла назначения с помощью флага -n:

mv -n file1 file2
cp -n file1 file2 

Похожие вопросы предполагают проверку успеха mv и cp с использованием кода завершения, который равен 0, если операция успешна, и >0, если произошла ошибка. Тем не менее, для обеих версий mv/cp код завершения равен 0, когда файл назначения уже существует, и используется флаг -n.

Единственный другой вариант, который я могу придумать, это также использовать флаг -v и посмотреть на вывод команды:

mv -nv file1 file2
cp -nv file1 file2

Тем не менее, версии GNU и BSD mv/cp ведут себя по-разному, когда используются флаги -nv, и файл file2 уже существует: версии GNU mv/cp ничего не возвращают, в то время как версии BSD возвращают file2 not overwritten.

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

Существует ли способ выполнить эту задачу, который будет работать с версиями mv/cp как BSD, так и GNU?

В качестве альтернативы, есть ли способ сделать это на Python 2? Я не смог найти способ сделать это с помощью os.rename()

Если у вас есть bashhttps://stackoverflow.com/questions/13828544/atomic-create-file-if-not-exists-from-bash-script

set -o noclobber
{ > file ; } &> /dev/null

Эта команда создает файл с именем file, если не существует файла с именем file. Если файл с именем file существует, то ничего не делается (но возвращается ненулевой код возврата).

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

Аналогично и для python. Используйте os.open(), чтобы создать пустой файл, обязательно включив O_EXCL в флаги. (“Для описания флага и значений режима смотрите документацию по выполнению C.” Смотрите стандарт POSIX / страница man Linux).


Техника bash использует O_EXCL за кулисами. Существует также RENAME_NOREPLACE, но это относительно недавнее дополнение в Linux, и я не думаю, что оно присутствует на OS X.

Если файлы находятся на одной файловой системе, то вы можете создать жесткую ссылку.

ln SRC DEST

Если это успешно, вы можете удалить исходный файл.

rm SRC

FreeBSD действительно возвращает ошибку, если cp -n запрашивается для перезаписи файла:

$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # это должно завершиться неудачно
$ cp -n foo.1 foo.2 || echo fail
fail
$ rm foo.2
$ # это должно завершиться успешно
$ cp -n foo.1 foo.2 || echo fail
$ exit

Вы правы, когда говорите, что mv FreeBSD возвращает успех, даже когда файл назначения существует:

$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # это должно завершиться неудачно
$ mv -n foo.1 foo.2 || echo fail
$ exit

Одно из решений – это && код результата mv с [ ! -f src-file ], как в:

$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # это должно завершиться неудачно
$ ( mv -n foo.1 foo.2 && [ ! -f foo.1 ] ) || echo fail
fail
$ rm foo.2
$ # это должно завершиться успешно
$ ( mv -n foo.1 foo.2 && [ ! -f foo.1 ] ) || echo fail
$ exit

В GNU ни одна утилита не работает так, как вам бы хотелось. То же решение для mv работает у меня на Ubuntu, так что остается только GNU cp как единственная оставшаяся проблема.

Просто в качестве примечания, я слышу ваш комментарий о состоянии гонки при проверке файла назначения перед вызовом cp, но мне кажется, что такое же состояние гонки будет присутствовать, даже если cp сделает всё правильно. Окно возможностей может быть меньше, но моя интуиция подсказывает мне, что оно всё равно будет существовать. Однако я не эксперт в этом вопросе.

Поскольку обходное решение для mv работает на обеих платформах, возможно, это обходное решение будет достаточным:

$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # это должно завершиться неудачно
$ ( cp -n foo.1 TEMPFILE && mv -n TEMPFILE foo.2 && [ ! -f TEMPFILE ] ) || echo fail
fail
$ rm -f TEMPFILE
$ rm foo.2
$ # это должно завершиться успешно
$ ( cp -n foo.1 TEMPFILE && mv -n TEMPFILE foo.2 && [ ! -f TEMPFILE ] ) || echo fail
$ rm -f TEMPFILE
$ exit

Другой способ достичь вашей цели:

“переименовать или скопировать файл, не перезаписывая файл назначения, если он существует”

Используйте -b или --backup[=CONTROL] при использовании ‘копирования’ (cp) или ‘перемещения’ (mv).

Это идеально подходит для использования в автоматизированных скриптах, так как делает безопасный выбор; т.е. не перезаписывает потенциально важные файлы. Документация находится здесь (для cp), и здесь (для опций backup в частности)

Что касается требований вашей платформы:

“версии mv/cp BSD, установленные на MacOS/Unix, а также версии GNU coreutils, которые у меня есть на Linux”

Опция -b, --backup (насколько я знаю) доступна только в версиях cp и mv от GNU coreutils. Как обычно, Apple отстает или отстаёт, избегая новых условий лицензирования GPL… ОДНАКО, существуют варианты для пользователей Apple/macOS: MacPorts предлагает пакет coreutils, или возможно через один из других менеджеров пакетов macOS.

Но если вы застряли с версией BSD (т.е. без опций backup), альтернативой может быть использование опции -i – которая вызывает запрос к stderr. Возможно, с помощью динамичного скрипта можно было бы реализовать пользовательский ‘резервный’ вариант.

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

Решение проблемы: Перемещение или копирование файлов без перезаписи и проверка успешности операции

Проблема, с которой вы столкнулись, касается необходимости безопасного переименования или копирования файла без перезаписи существующего файла в системе Unix или GNU/Linux. В данном ответе мы рассмотрим решения, которые работают как с BSD вериями команд mv и cp (на MacOS), так и с вериями GNU в Linux.

Использование флага -n

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

mv -n file1 file2
cp -n file1 file2

Однако, важно учитывать, что при использовании этого флага в GNU cp и mv, если целевой файл уже существует, команда вернет код завершения 0 (успех). Это создает затруднение, поскольку вы не сможете узнать, была ли выполнена операция.

Проверка существования файла перед операцией

Ранее вы упомянули использование проверки существования целевого файла перед выполнением перемещения или копирования. Но действительно, такая последовательность может привести к состоянию гонки, если другой процесс создаст файл в тот момент, когда вы делаете проверку.

Альтернативный подход через создание временного файла

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

cp -n file1 temp_file && mv -n temp_file file2 && [ ! -f temp_file ]

Этот подход позволяет вам безопасно убедиться, что цель не была перезаписана. Если cp завершается неудачно из-за существования file2, путь выполнения не доходит до mv, и временный файл не будет удален, что должно позволить вам выдать соответствующее сообщение об ошибке.

Использование Python для копирования без перезаписи

Если вы хотите решить задачу с помощью Python 2, рекомендую воспользоваться модулем os для работы с файлами. Используйте следующие шаги для создания нового файла только в том случае, если он не существует:

import os

src = 'file1'
dst = 'file2'

if not os.path.exists(dst):
    os.rename(src, dst)  # или os.copy(src, dst) для копирования
    print("Операция выполнена успешно.")
else:
    print("Ошибки: файл назначения уже существует.")

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

Заключение

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

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

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