
У меня есть плата, на которой запущен скрипт "patch". Скрипт patch всегда работает в фоновом режиме и представляет собой скрипт оболочки, который запускает следующий псевдокод:
while true; do
# checks if a patch tar file exists and if yes then do patching
sleep 10
done
Этот скрипт находится в /opt/patch.sh и запускается скриптом инициализации SystemV.
Проблема в том, что когда скрипт находит tar, он извлекает его, а внутри находится скрипт оболочки, который называетсяпатч.shкоторый специфичен для содержания смолы.
Когда сценарий в/opt/patch.shнаходит tar, делает следующее:
tar -xf /opt/update.tar -C /mnt/update
mv /mnt/update/patch.sh /opt/patch.sh
exec /opt/patch.sh
Он заменяет себя другим скриптом и выполняет его из того же места. Могут ли возникнуть какие-либо проблемы при этом?
решение1
Если файл заменяется путем записи на месте (inode остается прежним), любые процессы, которые его открывают, увидят новые данные, если/когда они будут читать из файла. Если он заменяется путем отсоединения старого файла и создания нового с тем же именем, номер inode изменится, и любые процессы, которые держат файл открытым, по-прежнему будут иметьстарыйфайл.
mv
может сделать и то, и другое, в зависимости от того, происходит ли перемещение между файловыми системами или нет... Чтобы убедиться, что вы получаете совершенно новый файл, сначала отсоедините или переименуйте исходный. Что-то вроде этого:
mv /opt/patch.sh /opt/patch.sh.old # or rm
mv /mnt/update/patch.sh /opt/patch.sh
Таким образом, запущенная оболочка по-прежнему будет иметь дескриптор файла для старых данных даже после перемещения.
Тем не менее, насколько я могу судить, Bash считывает весь цикл перед выполнением любой его части, поэтому любые изменения в базовом файле не приведут к изменению работающего скрипта, пока выполнение остается в пределах цикла. (Перед выполнением цикла необходимо прочитать весь цикл, поскольку в конце могут быть перенаправления, влияющие на весь цикл.)
После выхода из цикла Bash перемещает указатель чтения обратно, а затем возобновляет чтение входного файла с позиции сразу после завершения цикла.
Все функции, определенные в скрипте, также загружаются в память, поэтому помещение основной логики скрипта в функцию и ее вызов только в конце сделают скрипт достаточно защищенным от изменений в файле:
#!/bin/sh
main() {
do_stuff
exit
}
main
В любом случае, не так уж и сложно проверить, что произойдёт при перезаписи скрипта:
$ cat > old.sh <<'EOF'
#!/bin/bash
for i in 1 2 3 4 ; do
# rm old.sh
cat new.sh > old.sh
sleep 1
echo $i
done
echo will this be reached?
EOF
$ cat > new.sh <<'EOF'
#!/bin/bash
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EOF
$ bash old.sh
С rm old.sh
комментарием скрипт будет изменен на месте. Без комментария будет создан новый файл. (Этот пример частично основан на том, new.sh
что он больше, чем old.sh
, так как если бы он был короче, позиция чтения оболочки была бы за концом нового скрипта после цикла.)
решение2
У меня уже была эта проблема, и я могу подтвердить, что это может быть проблемой. В моем случае регрессионный скрипт сначала выполняет git pull и может обновиться после того, как он начнет работать, что приведет к проблемам.
Проблема обычно возникает, когда оболочка возвращается и проверяет, есть ли еще строки для интерпретации. Это может привести к ошибке, даже если нужный код находится внутри цикла. Чтобы избежать этого, используйте структуру вэта почта.
решение3
Самоисполняющийся, самомодифицирующийся скрипт? Это не очень хорошая идея.
Лучшим решением было бы создать заглушку-демона с минимальной функциональностью (т.е. отвечающую за установку новых версий подчиненного скрипта и его вызов с интервалами). Что-то вроде ... (не тестировалось)
while true; do
# check if a patch tar file exists and if yes then do patching
if [ -f "$PATCH" ]; then
( cd /usr/local/mydaemon \
&& tar -xzf "$PATCH" \
&& rm -f "$PATCH" ) \
|| exit -1
fi
$SCRIPT
sleep 10
done