UNIX атомарно меняет местами или заменяет каталоги?

У меня следующая проблема. У меня есть два каталога, которые я буду называть dir1 и dir2. Они оба заполнены как файлами, так и подкаталогами. Они оба находятся в одной файловой системе. Содержимое dir1 устарело и должно быть заменено содержимым dir2. Содержимое dir1 можно удалить. Однако мне нужно, чтобы эта замена происходила атомарно. Мое текущее решение заключается в следующем.

mv dir1 dir1_backup; mv dir2 dir1; rm -rf dir1_backup

Если первая команда завершится ошибкой, вторая команда не запустится. Это хорошо. Однако что, если вторая команда не сработает? dir1 больше не существует, и я остался в недопустимом состоянии.

Каков наилучший способ добиться этого? Является ли это возможным?


person John    schedule 14.06.2013    source источник
comment
Может быть слишком поздно — простой способ атомарного обмена каталогами — через символическую ссылку.   -  person Carl Norum    schedule 14.06.2013
comment
возможный дубликат атомарного перемещения каталога   -  person Carl Norum    schedule 14.06.2013
comment
Разве вы не имеете в виду && вместо ; для разделения команд? К сожалению, несмотря на то, что && должен работать, я не уверен, что когда-нибудь захочу делать ставку на магазин таким образом. Я бы создал что-то, что выполняет проверки, особенно перед последним rm шагом. Удачи.   -  person shellter    schedule 14.06.2013
comment
Я видел - stackoverflow.com/questions/307437/ - до. Решения, представленные в нем, терпят неудачу в моем крайнем случае.   -  person John    schedule 14.06.2013
comment
Хотелось бы объяснить, почему использование символических ссылок и выполнение mv -T point_to_dir2 point_to_dir1 не сработает в вашем крайнем случае.   -  person Fredrik Wendt    schedule 25.05.2016


Ответы (3)


Linux предоставляет системный вызов renameat2, который с флагом RENAME_EXCHANGE меняет местами 2 объекта:

renameat2(AT_FDCWD, "dir1", AT_FDCWD, "dir2", RENAME_EXCHANGE);
person Timothy Baldwin    schedule 29.08.2020
comment
Если кому-то нужен исполняемый файл оболочки для renameat2, я написал его. Вы можете загрузить его с github.com/AbhyudayaSharma/exchange. - person Abhyudaya Sharma; 26.05.2021

Я не думаю, что ваше утверждение выше о том, что вторая команда потерпит неудачу, если первая потерпит неудачу, верно. Если mv dir1 dir1_backup потерпит неудачу, то dir1 все еще будет существовать, а mv dir2 dir1 сделает dir2 подкаталогом dir1. Кроме того, поскольку вы передаете -f в rm в третьей команде, он не сможет удалить несуществующий каталог.

Для справки, три оператора bash (здесь предполагается bash) и что они делают:

  • ; просто выполняет следующую команду после первого выхода, независимо от статуса выхода.
  • && выполняет следующую команду только в том случае, если предыдущая команда завершилась корректно (т. е. имела статус выхода 0).
  • || выполняет следующую команду только в том случае, если предыдущая команда завершилась некорректно (т. е. имела ненулевой код выхода).

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

    mv dir1 dir1_backup && mv dir2 dir1 && rm -rf dir1_backup

Если перемещение каталога является режимом отказа, который вы ожидаете, то может быть разумно проверить возвращаемое значение перемещения или проверить существование ожидаемого каталога, прежде чем приступить к удалению старого содержимого каталога. Если тест не пройден, верните старое содержимое каталога. Общая операция завершится неудачно, но, по крайней мере, вы не окажетесь в недопустимом состоянии. Например:

    mv dir1 dir1_backup && \
    mv dir2 dir1 && \
    rm -rf dir1_backup || mv dir1_backup dir1

Поскольку мы знаем, что && выполняет следующую команду только в случае успешного завершения предыдущей команды, mv dir2 dir1 будет выполняться только в случае успеха mv dir1 dir1_backup.

Кроме того, rm -rf dir1_backup будет выполняться только в том случае, если перемещение каталога dir2 в каталог dir1 будет успешным. Однако, если последний код выхода (код, возвращаемый mv dir2 dir1 в случае сбоя) не равен нулю, оператор || вызывает выполнение mv dir1_backup dir1.

Надеюсь, это поможет! Дайте мне знать, если это нуждается в разъяснении.

person cfunk    schedule 14.06.2013
comment
cfunk, спасибо, что прояснил это. Я думал так ; и && сделали противоположное тому, что они на самом деле делают. Ваше решение ближе к тому, что я хочу, но есть угловой случай. Первая команда выполнена успешно. Вторая команда завершается неудачно, вызывая последнюю команду. (mv dir1_backup dir1) Представьте, что ПОСЛЕДНЯЯ команда не удалась. Я все еще остаюсь в недопустимом состоянии: dir1 не существует. - person John; 14.06.2013
comment
Если вы можете переименовать каталог в первом случае, какой режим сбоя, по вашему мнению, не позволит вам вернуть его обратно? Если это ожидается, вы можете исследовать использование символических ссылок вместо перемещения каталогов. Если возможно, было бы лучше, чтобы приложение, обращающееся к каталогу, смотрело на новое местоположение и вообще не беспокоилось о перетасовке каталогов. - person cfunk; 15.06.2013

В Linux вам может сойти с рук монтирование нового каталога поверх старого. Однако у вас все еще будут проблемы с процессами, у которых есть открытый дескриптор каталога внутри старого каталога (например, их текущий рабочий каталог).

mount --bind dir2 dir1

Подобные трюки позволят вам получить доступ к старому каталогу для целей очистки.

Если эта файловая система экспортируется через NFS, вам, вероятно, потребуется убедиться, что параметры экспорта настроены так, чтобы разрешить пересечение границ файловой системы и выполнить монтирование привязки на сервере.

Другие *nix имеют аналогичные функции, но они не стандартизированы.

person William Hay    schedule 01.11.2019