Замена скрипта оболочки во время его выполнения

Замена скрипта оболочки во время его выполнения

У меня есть плата, на которой запущен скрипт "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

Связанный контент