Rai NewsSci, Soelden, gigante maschile: Pinheiro Braathen domina la prima manche. Diretta RaiNewsInquirer EntertainmentWinwyn Marquez, Mikee Quintos among new housemates of ‘PBB Collab 3.0’PunchPolice arrest tiler over death of hotel worker in AbujaThe Jerusalem PostArab Israeli voters are essential to Israel’s democratic future - opinionCNN TürkKaradeniz'de istilacı böcek kabusuna 'siber' kalkanInquirerFather, son shot dead over land dispute in QuezonESPNTransfer rumors, news: Arsenal, Man United, Liverpool track Bayern star Karl한겨레[사설] 독·일 합계도 넘어선 경상흑자, ‘반도체 이후’ 준비를Daily MaverickTIP-OFF AND BUST: Baobab trees seized in Durban ship warehouse probeZDF heuteAktuelle Pressemitteilungen des ZDFUOLComparecimento sobe na China continental, e Lula vence com 70,5% dos votosSCMP ChinaHow China’s regional leadership changes offer clues to the future of the Communist Party
The Daily Newsstand · Free, Always
Sunday, October 25, 2026

Обновил файл, а читатель видит старое: сравнение cp, sed -i и os.replace

Translate

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

Я сравнил несколько способов обновить маленький файл: перезапись на месте, cp, sed -i, install и замену через временный файл. Это синтетический лабораторный опыт на Linux. Проверяемое поведение описано в документации; цель опыта — сравнить последствия нескольких команд в одинаковых условиях.

Стенд

Python 3.15.0, GNU sed 4.9 и GNU coreutils 9.7 в контейнере, файловая система overlayfs. Запуск от обычного пользователя (UID 1002), umask 022. Каждый вариант работал в отдельном каталоге. Исходный файл config имел права 0640 и дополнительную жёсткую ссылку, а один читатель заранее держал его открытым. Сначала выполнялось обновление, и только после его завершения читатель начинал читать. Одновременного чтения и записи в опыте не было.

Что получилось

Способ обновления

Права после

inode прежний

По исходному пути

Уже открытый дескриптор

Жёсткая ссылка

open(config, 'w'), запись на месте

0640

да

новый

новый

новый

cp source config

0640

да

новый

новый

новый

sed -i 's/old/new/' config

0640

нет

новый

старый

старый

cp --remove-destination source config

0644

нет

новый

старый

старый

install -m 640 source config

0640

нет

новый

старый

старый

open(temp, 'w'), затем os.replace

0644

нет

новый

старый

старый

mkstemp, затем os.replace

0600

нет

новый

старый

старый

mkstemp, fchmod(0640), затем os.replace

0640

нет

новый

старый

старый

Таблица делится на две группы. Если inode остаётся прежним, как при записи на месте или обычном cp, новые данные после окончания записи видят все: путь, открытый дескриптор и жёсткая ссылка. Если под именем появляется новый файл (sed -i, install, cp --remove-destination, os.replace), новое видно только по пути. Открытый дескриптор и жёсткая ссылка остаются на прежнем inode со старым текстом.

Механизм документирован. GNU sed для -i пишет результат во временный файл, который потом занимает место исходного. В описании rename(2) сказано, что открытые дескрипторы переименование не затрагивает. sed -i при этом сохранил 0640, поэтому проверка одних прав через stat не показывает, какую версию прочитает процесс с открытым файлом.

Права зависят от того, как создан новый файл. Обычный cp писал в уже существующий файл и сохранил его 0640. С --remove-destination файл создаётся заново и в этом стенде получил 0644. Текст по пути одинаковый, поэтому команды легко принять за взаимозаменяемые, хотя это не так.

Символьные ссылки

Отдельно проверил случай, когда config является символьной ссылкой на настоящий файл:

Способ

Осталась ссылкой

Чтение через путь

Чтение файла-цели

запись на месте

да

новый

новый

os.replace

нет

новый

старый

os.replace заменяет саму ссылку обычным файлом, а файл, на который она указывала, остаётся со старым содержимым.

Короткий repro

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

import os
import stat
import tempfile
from pathlib import Path

os.umask(0o022)
with tempfile.TemporaryDirectory() as directory:
    path = Path(directory) / 'config'
    path.write_text('old\n')
    path.chmod(0o640)

    with path.open() as reader:
        fd, name = tempfile.mkstemp(dir=directory)
        with os.fdopen(fd, 'w') as writer:
            writer.write('new\n')
        os.replace(name, path)

        print(oct(stat.S_IMODE(path.stat().st_mode)))
        print(path.read_text().strip())
        print(reader.read().strip())

Программа печатает 0o600, new, old. По документации tempfile файл от mkstemp доступен на чтение и запись только создателю. Для временных файлов это полезно, но после замены конфига с 0640 группа теряет право на чтение. Временный файл, созданный обычным open(..., 'w'), при umask 022 получает 0644, то есть доступ расширяется. В варианте с сохранением режима я перед заменой вызвал os.fchmod(fd, 0o640), и нужные биты остались.

Что проверять в скрипте обновления

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

Если потребитель держит дескриптор открытым, замена файла по имени новую версию ему не покажет. Нужен механизм приложения: повторное открытие или перезагрузка конфигурации. Жёсткие ссылки после замены сохранят прежний inode. Если заменяемое имя было символьной ссылкой, она исчезнет, а файл-цель останется прежним.

Из таблицы не следует, что обычный cp или запись на месте безопаснее. Опыт не проверял, что увидит читатель во время перезаписи.

Ограничения

  • Одна машина, overlayfs, обычный пользователь, umask 022, маленький файл.

  • Не проверялись одновременное чтение во время записи, отключение питания и сбои, владелец файла, ACL, расширенные атрибуты, SELinux и сетевые файловые системы.

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

  • Атомарная замена имени не означает, что сохраняются все свойства файла, и не гарантирует сохранность данных после сбоя питания.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.

Обновил файл, а читатель видит старое: сравнение cp, sed -i и os.replace — KioskNews