Substituindo o script de shell ao executá-lo

Substituindo o script de shell ao executá-lo

Eu tenho uma placa que está executando um script de "patch". O script de patch sempre é executado em segundo plano e é um script de shell que executa o seguinte pseudocódigo:

while true; do
    # checks if a patch tar file exists and if yes then do patching
    sleep 10
done

Este script está em /opt/patch.sh e foi iniciado pelo script de inicialização do SystemV.

O problema é que quando o script encontra o tar, ele o extrai, e dentro dele existe um shell script chamadopatch.shque é específico para o conteúdo do tar.

Quando o roteiro em/opt/patch.shencontra o tar e faz o seguinte:

tar -xf /opt/update.tar -C /mnt/update
mv /mnt/update/patch.sh /opt/patch.sh
exec /opt/patch.sh

Ele se substitui por outro script e o executa no mesmo local. Pode ocorrer algum problema fazendo isso?

Responder1

Se o arquivo for substituído por gravação no local (o inode permanece o mesmo), qualquer processo que o abra verá os novos dados se/quando eles lerem o arquivo. Se ele for substituído desvinculando o arquivo antigo e criando um novo com o mesmo nome, o número do inode muda e qualquer processo que mantenha o arquivo aberto ainda terá ovelhoarquivo.

mvpode fazer qualquer um, dependendo se a movimentação ocorre entre sistemas de arquivos ou não... Para garantir que você obtenha um arquivo completamente novo, desvincule ou renomeie o original primeiro. Algo assim:

mv /opt/patch.sh /opt/patch.sh.old     # or rm
mv /mnt/update/patch.sh /opt/patch.sh

Dessa forma, o shell em execução ainda teria um identificador de arquivo para os dados antigos, mesmo após a movimentação.


Dito isto, pelo que testei, o Bash lê o loop inteiro antes de executar qualquer um deles, portanto, qualquer alteração no arquivo subjacente não alteraria o script em execução, desde que a execução permaneça dentro do loop. (É necessário ler todo o loop antes de executá-lo, pois pode haver redirecionamentos afetando todo o loop no final.)

Depois de sair do loop, Bash move o ponteiro de leitura de volta e retoma a leitura do arquivo de entrada a partir da posição logo após o término do loop.

Quaisquer funções definidas no script também são carregadas na memória, portanto, colocar a lógica principal do script em uma função e apenas chamá-la no final tornaria o script bastante seguro contra modificações no arquivo:

#!/bin/sh
main() {
    do_stuff
    exit
}
main

De qualquer forma, não é muito difícil testar o que acontece quando um script é sobrescrito:

$ 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

Com o rm old.shcomentário comentado, o script será alterado no local. Sem o comentário, um novo arquivo será criado. (Este exemplo depende parcialmente de new.shser maior que old.sh, como se fosse mais curto, a posição de leitura do shell passaria do final do novo script após o loop.)

Responder2

Já tive esse problema antes e posso confirmar que isso pode ser um problema. No meu caso, um script de regressão primeiro executa um git pull e pode ser atualizado depois de começar a ser executado, causando problemas.

O problema geralmente é quando o shell volta e verifica se há mais linhas para interpretar. Isso pode causar erros mesmo quando o código desejado está dentro de um loop. Para evitar isso, use a estrutura emesta postagem.

Responder3

Um script autoexecutável e automodificável? Isso não é uma boa idéia.

Uma solução melhor seria criar um daemon stub com funcionalidade mínima (ou seja, responsável por instalar novas versões do script escravo e invocá-lo em intervalos). Algo como... (não testado)

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

informação relacionada