Как происходит модификация файла inplace в файле?

Что означает «inplace» модификация файла, например, через sed -i или perl -i ?
Мой вопрос заключается в том, как это делается на месте. Скопирован ли файл, модификация выполняется в копии, а затем заменить оригинал? Или исходный файл каким-то образом модифицируется?

sed создает временный файл, записывает вывод в этот файл и затем переименовывает временный файл поверх оригинала.

Вы можете посмотреть, что происходит, используя strace :

 $ strace -e trace=file sed -i -e '' a execve("/usr/bin/sed", ["sed", "-i", "-e", "", "a"], [/* 34 vars */]) = 0 <...trimmed...> open("a", O_RDONLY) = 3 open("./sedxvhRY8", O_RDWR|O_CREAT|O_EXCL, 0600) = 4 rename("./sedxvhRY8", "a") = 0 +++ exited with 0 +++ 

Это регистрирует все операции с файлами sed : он создает новый файл (безопасно с O_CREAT|O_EXCL ), записывает в него данные и затем переводит его обратно поверх моего исходного файла a .

sed -i принимает суффикс для использования в резервной копии, и в этом случае сначала он переносит оригинал (вместо того, чтобы переименовывать поверх него). Этот аргумент является обязательным в большинстве BSD sed . В этом случае есть короткое время, когда в каталоге нет файла по правому имени.

perl в последних версиях открывает входной файл, затем удаляет его и создает новый файл с тем же именем:

 open("a", O_RDONLY) = 3 unlink("a") = 0 open("a", O_WRONLY|O_CREAT|O_EXCL, 0600) = 4 

Когда вы удаляете ( unlink ) файл, который у вас уже открыт, вы сохраняете доступ к нему до тех пор, пока вы держите дескриптор, чтобы он мог читать данные из удаленного файла. Таким образом, perl записывает непосредственно в выходной файл, а не во временный файл: никакой дополнительный файл не создается, но если вы прочитаете файл во время процесса, вы получите частичный контент, в отличие от подхода sed . Также есть короткое время, когда нет файла с правильным именем, которое находится в начале процесса, а не в конце (как в sed -i .bak ).


Оба sed и perl будут:

  • Замените символическую ссылку обычным файлом.
  • Перерыв жестких ссылок.
  • При необходимости сохраните групповое владение.
  • Создайте файл с вашей группой по умолчанию (или группой родительского каталога, если этот каталог имеет бит setgid ), если он принадлежит группе, в которой вы не находитесь, и вы не являетесь пользователем root.
  • Сохранять права собственности на файлы, если вы являетесь пользователем root.
  • Сохраните основные разрешения.
  • Сохраните бит setuid и setgrp , если результирующая группа будет такой же, как и в группе, в которой она была запущена.
  • Сохраните липкий бит.
  • Не сохранять xattrs.

sed будет:

  • Сохранить ACL (в Linux, я не знаю о других) .

perl будет:

  • Не сохранять ACL.

Вышеупомянутое верно для Linux с GNU sed и Mac OS X с его (полученным от FreeBSD) sed .

В дополнение к ответу Гомера от perldoc perlrun :

указывает, что файлы, обработанные конструкцией «<>», должны быть отредактированы на месте. Он делает это, переименовывая входной файл, открывая выходной файл исходным именем и выбирая этот выходной файл в качестве операторов по умолчанию для print (). Расширение, если оно указано, используется для изменения имени старого файла для создания резервной копии, следуя этим правилам:

Если расширение не добавлено, резервная копия не производится и текущий файл перезаписывается.

Если расширение не содержит *, то оно добавляется к концу текущего имени файла как суффикс. Если расширение содержит один или несколько символов *, то каждый * заменяется текущим именем файла.

И помните, что никакого мягкого канала или жесткой ссылки не сохраняется:

Обратите внимание, что поскольку -i переименовывает или удаляет исходный файл перед созданием нового файла с таким же именем, мягкие и жесткие ссылки в стиле UNIX не будут сохранены.

Наконец, ключ -i не препятствует выполнению, если в командной строке не указаны файлы. В этом случае резервное копирование не производится (конечно, исходный файл не может быть определен), и обработка переходит от STDIN к STDOUT, как и следовало ожидать.

Это также объясняет, почему вы должны использовать -i с опцией -p или использовать явный оператор print если вы хотите редактировать inplace с помощью perl :

 # Opps, file will be truncated, becomes empty $ perl -i.bak -ne 's/123/qwe/' file # Right way $ perl -i.bak -ne 's/123/qwe/;print' file # Or $ perl -i.bak -pe 's/123/qwe/' file