Как бороться с задержкой NFS в сценариях оболочки
Вопрос
Я пишу сценарии оболочки, где довольно регулярно некоторые вещи записываются в файл, после чего выполняется приложение, которое считывает этот файл.Я обнаружил, что в нашей компании задержка в сети сильно различается, поэтому простой sleep 2
например, не будет достаточно надежным.
Я попытался написать (настраиваемый) цикл тайм-аута следующим образом:
waitLoop()
{
local timeout=$1
local test="$2"
if ! $test
then
local counter=0
while ! $test && [ $counter -lt $timeout ]
do
sleep 1
((counter++))
done
if ! $test
then
exit 1
fi
fi
}
Это работает для test="[ -e $somefilename ]"
.Однако проверки существования недостаточно, иногда мне нужно проверить, была ли записана в файл определенная строка.Я пыталсяtest="grep -sq \"^sometext$\" $somefilename"
, но это не сработало.Может кто-нибудь сказать мне, почему?
Существуют ли другие, менее подробные варианты выполнения такого теста?
Решение
Вы можете установить тестовую переменную следующим образом:
test=$(grep -sq "^sometext$" $somefilename)
Причина вашего grep
не работает, заключается в том, что кавычки очень сложно передавать в качестве аргументов.Вам нужно будет использовать eval
:
if ! eval $test
Другие советы
я бы сказал тот способ проверить наличие строки в текстовом файле — grep.
В чем конкретно у вас проблема?
Также вы можете настроить параметры монтирования NFS, чтобы избавиться от корневой проблемы.Синхронизация также может помочь.См. документацию NFS.
Если вы хотите использовать waitLoop в «if», вы можете изменить «exit» на «return», чтобы остальная часть сценария могла обрабатывать ситуацию с ошибкой (пользователю даже не будет сообщено о что не удалось, прежде чем сценарий умрет, в противном случае).
Другая проблема заключается в том, что использование «$test» для хранения команды означает, что вы не получаете расширение оболочки при фактическом выполнении, а просто оцениваете.Таким образом, если вы скажете test="grep \"foo\" \"bar baz\"", вместо того, чтобы искать трехбуквенную строку foo в файле с семизначным именем bar baz, он будет искать строку из пяти символов. «foo» в девятисимвольном файле «bar baz».
Таким образом, вы можете либо решить, что вам не нужна магия оболочки, и установить test='grep -sq ^sometext$ somefilename', либо вы можете заставить оболочку явно обрабатывать цитирование, например:
if /bin/sh -c "$test"
then
...
Попробуйте использовать время изменения файла, чтобы определить, когда он записывается, не открывая его.Что-то вроде
old_mtime=`stat --format="%Z" file`
# Write to file.
new_mtime=$old_mtime
while [[ "$old_mtime" -eq "$new_mtime" ]]; do
sleep 2;
new_mtime=`stat --format="%Z" file`
done
Однако это не сработает, если несколько процессов попытаются получить доступ к файлу одновременно.
У меня была точно такая же проблема.Я использовал аналогичный подход к ожиданию тайм-аута, который вы включаете в свой OP;однако я также включил проверку размера файла.Я сбрасываю таймер тайм-аута, если размер файла увеличился с момента последней его проверки.Файлы, которые я пишу, могут занимать несколько гигабайт, поэтому их запись в NFS занимает некоторое время.
Это может быть излишним для вашего конкретного случая, но я также заставил свой процесс записи вычислить хэш файла после его завершения.Я использовал md5, но что-то вроде crc32 тоже подойдет.Этот хэш был передан от записывающего устройства (несколько) читателям, и читатель ждет, пока а) размер файла не перестанет увеличиваться и б) (свежевычисленный) хэш файла не будет соответствовать хэшу, отправленному записывающим устройством.
У нас похожая проблема, но по другим причинам.Мы читаем файл, который отправляется на SFTP-сервер.Машина, на которой выполняется сценарий, не является SFTP-сервером.
Что я сделал, так это настроил его в cron (хотя цикл со спящим режимом тоже подойдет) для подсчета контрольной суммы файла.Когда старая cksum совпадает с текущей (файл не менялся в течение определенного периода времени), мы знаем, что запись завершена, и передаем файл.
В целях большей безопасности мы никогда не перезаписываем локальный файл перед созданием резервной копии и выполняем передачу только тогда, когда удаленный файл имеет две совпадающие cksums подряд, и эта cksum не соответствует локальному файлу.
Если вам нужны примеры кода, я уверен, что смогу их найти.
Оболочка разбивала ваш предикат на слова.Возьмите все это с $@
как в коде ниже:
#! /bin/bash
waitFor()
{
local tries=$1
shift
local predicate="$@"
while [ $tries -ge 1 ]; do
(( tries-- ))
if $predicate >/dev/null 2>&1; then
return
else
[ $tries -gt 0 ] && sleep 1
fi
done
exit 1
}
pred='[ -e /etc/passwd ]'
waitFor 5 $pred
echo "$pred satisfied"
rm -f /tmp/baz
(sleep 2; echo blahblah >>/tmp/baz) &
(sleep 4; echo hasfoo >>/tmp/baz) &
pred='grep ^hasfoo /tmp/baz'
waitFor 5 $pred
echo "$pred satisfied"
Выход:
$ ./waitngo [ -e /etc/passwd ] satisfied grep ^hasfoo /tmp/baz satisfied
Жаль, что машинописный текст не так интересен, как смотреть его в реальном времени.
Ладно... это немного странно...
Если у вас есть контроль над файлом:возможно, вы сможете создать здесь «именованный канал».Таким образом (в зависимости от того, как работает программа записи) вы можете отслеживать файл синхронно.
В самом простом виде:
Создайте именованный канал:
mkfifo file.txt
Настройте синхронизируемый приемник:
while :
do
process.sh < file.txt
end
Создайте тестового отправителя:
echo "Hello There" > file.txt
В «process.sh» находится ваша логика:это будет блокироваться до тех пор, пока отправитель не запишет свой вывод.Теоретически программа записи не нуждается в модификации....
ПРЕДУПРЕЖДЕНИЕ:если получатель по какой-то причине не работает, вы можете заблокировать отправителя!
Не уверен, что это соответствует вашим требованиям, но, возможно, стоит изучить.
Или, чтобы избежать синхронизации, попробуйте «lsof»?
http://en.wikipedia.org/wiki/Lsof
Предполагая, что вы хотите читать из файла только тогда, когда в него ничего не записывается (т. е. процесс записи завершен) - вы можете проверить, не имеет ли ничего другого дескриптора файла?