
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.
mv
pode 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.sh
comentário comentado, o script será alterado no local. Sem o comentário, um novo arquivo será criado. (Este exemplo depende parcialmente de new.sh
ser 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