1

Я пытаюсь записать в один файл "file.cfg" значения двух переменных, сгенерированных двумя независимыми сценариями. Две переменные постоянно обновляются и сохраняются в «file.cfg». Ниже приведен пример моей работы.

пример содержимого "file.cfg":

a=null
b=null

пример "script_a.sh" обновить значение "a" с помощью:

#!/bin/bash
while : do
    .............
    val_a=1 
    sed -i "s/^\(a=\).*/\1$val_a/" file.cfg
    .............
done

пример "script_b.sh" обновляет значение "b" с помощью:

#!/bin/bash
while : do
    .............
    val_b=2 
    sed -i "s/^\(b=\).*/\1$val_b/" file.cfg
    .............
done

Сценарии работают отлично, а значения обновляются. Но если два сценария выполняются одновременно, одно из двух значений не обновляется.

Я обнаружил, что sed с опцией "-i" создает временный файл, который перезаписывается двумя одновременными операциями. Как я могу решить?

4 ответа4

1

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

Если у вас есть пакет lockfile-progs , вы можете использовать его для проверки существующей действительной блокировки (в течение последних 5 минут) с помощью lockfile-check и аналогичных lockfile-create & lockfile-remove .

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

lockfile-create есть задержка по умолчанию, если файл блокировки уже существует, прежде чем продолжить, он будет ждать, пока файл не будет разблокирован. Вот исключение из справочной страницы:

-r retry-count, --retry retry-count

Попробуйте заблокировать время повторного подсчета имени файла, прежде чем сдаться. Каждая попытка будет задержана немного дольше, чем последняя (с шагом 5 секунд), пока не будет достигнута максимальная задержка в одну минуту между повторными попытками. Если число повторов не указано, по умолчанию используется значение 9, которое прекращается через 180 секунд (3 минуты), если все 9 попыток блокировки завершатся неудачно.

Вот базовый пример, разрешающий использование нескольких команд, пока файл file.cfg заблокирован (включая выход при сбое lockfile-create ), но смотрите страницу man для более подробной информации.

lockfile-create file.cfg  || { echo "lockfile-create failed, exiting now"; exit; }
...
sed -i ... file.cfg
...
lockfile-remove file.cfg

Если вам нужен файл блокировки более 5 минут, используйте lockfile-touch чтобы «работать вечно, касаясь замка раз в минуту, пока не будет убит». Вот выдержка из справочной страницы:

Locking a file during a lengthy process:

     lockfile-create /some/file
     lockfile-touch /some/file &
     # Save the PID of the lockfile-touch process
     BADGER="$!"
     do-something-important-with /some/file
     kill "${BADGER}"
     lockfile-remove /some/file

Если вы действительно хотите сделать что-то особенное в ожидании разблокировки файла, вы можете использовать цикл while, подобный этому, но между проверкой и блокировкой файла может быть окно в несколько миллисекунд (0,003 с в моих time тестах), но тогда lockfile-create просто подождет, пока все будет безопасно.

while lockfile-check file.cfg
do
  echo doing stuff waiting for lock to clear
  sleep 1
done

lockfile-create file.cfg || exit
...
sed -i ... file.cfg
...
lockfile-remove file.cfg

И пока оба сценария используют и уважают файлы блокировки, sed никогда не сможет заменить файл, пока он разблокирован, поэтому не должно быть конфликтов копирования и переименования файлов.


Или есть другие похожие варианты, такие как:

  • dotlockfile
  • ваш собственный test -a FILE и touch ...
  • flock как в ответе Камиля, находится в пакете coreutils , что приятно
  • Сохраните значения в программе базы данных, которая может безопасно обрабатывать одновременный доступ
0

Это классическая проблема обновления в реальном времени: script_a.sh читает file.cfg , и перед тем, как записывать какие-либо изменения, script_b.sh читает ту же информацию; затем, какой бы скрипт ни записывал свое обновление первым, его изменения будут перезаписаны, когда другой скрипт публикует свое обновление. Неважно, были ли обновления сделаны во временном файле или путем прямой записи.

В bash нет собственной обработки семафоров или мьютексов, но вы можете использовать сам file.cfg , добавляя строки в ваши скрипты, например, в script_a.sh:-

#!/bin/bash
while : do
    .............
    while ! mv file.cfg file.cfg_a 2>/dev/nul; do sleep 0.1; done
    val_a=1
    sed -i "s/^\(a=\).*/\1$val_a/" file.cfg_a
    mv file.cfg_a file.cfg
    .............
done

Изменения в script_b.sh аналогичны, за исключением того, что файл переименован в file.cfg_b для обновления.

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

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

Обратите внимание, что некоторые версии sleep не поддерживают дробные задержки, и в этом случае вам нужно будет задержаться минимум на секунду перед повторной попыткой, если вы не используете другую утилиту.

0

Этот другой ответ использует идею lockfile. Есть еще одна утилита: flock(1) . Из его руководства:

flock [options] file|directory command [arguments]
flock [options] file|directory -c command
[...]

Эта утилита управляет блокировками flock(2) из сценариев оболочки или из командной строки.

Первая и вторая из вышеперечисленных форм обертывают блокировку вокруг выполнения команды способом, аналогичным su(1) или newgrp(1) . Они блокируют указанный файл или каталог, который создается (при условии соответствующих разрешений), если он еще не существует. По умолчанию, если блокировка не может быть немедленно получена, flock ждет, пока блокировка не будет доступна.

И поскольку он использует системный вызов flock(2), я считаю, что ядро гарантирует, что никакие два процесса не смогут удерживать блокировку для одного и того же файла:

LOCK_EX Разместите эксклюзивную блокировку. Только один процесс может удерживать монопольную блокировку для данного файла в данный момент времени.

В ваших скриптах вместо sed … запустите flock some_lockfile sed … , например

flock some_lockfile sed -i "s/^\(a=\).*/\1$val_a/" file.cfg

И все, замок открывается, когда выходит sed . Единственными недостатками являются:

  • some_lockfile уже используется в качестве файла блокировки; безопасный способ - использовать mktemp для создания временного файла и использовать его ;
  • в конце вам нужно удалить some_lockfile (я думаю, вы не хотите оставлять его как мусор); но если что-то еще использует файл (вероятно, не как файл блокировки), вы можете не захотеть его удалять; Опять же, mktemp - это путь: создать временный файл, использовать его, удалить его - независимо от того, что делают другие процессы.

Почему бы не flock file.cfg sed … тогда? Это заблокировало бы точный файл, который используется; это не оставило бы мусора вообще. Почему бы и нет?

Ну, потому что это недостатки. Чтобы понять это, давайте посмотрим, что именно (GNU) sed -i делает:

-i[SUFFIX]
--in-place[=SUFFIX]

Эта опция указывает, что файлы должны редактироваться на месте. GNU sed делает это, создавая временный файл и отправляя вывод в этот файл, а не в стандартный вывод.

[...]

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

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

Теперь рассмотрим следующий сценарий:

  1. Первая flock file.cfg sed -i … file.cfg блокирует оригинальный файл и работает с ним.
  2. До того, как первый sed завершится, возникает еще одна flock file.cfg sed -i … file.cfg . Эта новая flock нацелена на оригинальный file.cfg и ожидает первой блокировки, которая будет снята .
  3. Первый sed перемещает свой временный файл к исходному имени и завершает работу. Первый замок снят.
  4. Второе flock порождает второе sed которое теперь открывает новый file.cfg . Этот файл не является исходным файлом (из-за другого индекса). Но вторая flock предназначалась и блокировала исходный файл, а не тот, который только что открыл второй sed !
  5. Перед завершением второго sed возникает еще одна flock file.cfg sed -i … file.cfg . Это новое flock проверяет текущий file.cfg и обнаруживает, что оно не заблокировано ; он блокирует файл и порождает sed . Третий sed начинает читать текущий file.cfg .
  6. Теперь два процесса sed -i читают из одного файла параллельно. В зависимости от того, что заканчивается первым, проигрывает - другой перезапишет результаты, перенеся свою независимую копию в исходное имя.

Вот почему вам нужен some_lockfile с твердым номером инода.

0

Страница man sed

-i [SUFFIX], --in-place [= SUFFIX]

редактировать файлы на месте (делает резервную копию, если расширение предоставлено). Режим работы по умолчанию - разорвать символические и жесткие ссылки. Это можно изменить с помощью --follow-symlinks и --copy.

-c, --copy

используйте копирование вместо переименования при перетасовке файлов в режиме -i. Хотя это позволит избежать разрыва ссылок (символических или жестких), результирующая операция редактирования не является атомарной. Это редко желаемый режим; --follow-symlinks обычно достаточно, и это быстрее и безопаснее.

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

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .