Linux: agendar comando para executar uma vez após a reinicialização (equivalente ao RunOnce)

26

Gostaria de agendar um comando para ser executado após a reinicialização em uma caixa do Linux. Eu sei como fazer isso para que o comando seja executado consistentemente após cada reinicialização com uma @rebootentrada crontab, no entanto, eu quero que o comando seja executado apenas uma vez. Após a execução, ele deve ser removido da fila de comandos a serem executados. Estou procurando essencialmente um Linux equivalente ao RunOnce no mundo do Windows.

Caso isso importe:

$ uname -a
Linux devbox 2.6.27.19-5-default #1 SMP 2009-02-28 04:40:21 +0100 x86_64 x86_64 x86_64 GNU/Linux
$ bash --version
GNU bash, version 3.2.48(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2007 Free Software Foundation, Inc.
$ cat /etc/SuSE-release
SUSE Linux Enterprise Server 11 (x86_64)
VERSION = 11
PATCHLEVEL = 0

Existe uma maneira fácil e com scripts de fazer isso?

Christopher Parker
fonte
1
RunOnce é um artefato do Windows resultante de problemas na conclusão da configuração antes de uma reinicialização. Existe algum motivo para você não conseguir executar seu script antes da reinicialização? A solução acima parece ser um clone razoável do RunOnce.
BillThor

Respostas:

38

Crie uma @rebootentrada no seu crontab para executar um script chamado /usr/local/bin/runonce.

Crie uma estrutura de diretórios chamada /etc/local/runonce.d/ranusing mkdir -p.

Crie o script da /usr/local/bin/runonceseguinte maneira:

#!/bin/sh
for file in /etc/local/runonce.d/*
do
    if [ ! -f "$file" ]
    then
        continue
    fi
    "$file"
    mv "$file" "/etc/local/runonce.d/ran/$file.$(date +%Y%m%dT%H%M%S)"
    logger -t runonce -p local3.info "$file"
done

Agora colocar qualquer script que você deseja executar na próxima reinicialização (apenas uma vez) no diretório /etc/local/runonce.de chowne chmod +x-lo adequadamente. Depois de executado, você o verá movido para o ransubdiretório e a data e hora anexadas ao seu nome. Também haverá uma entrada no seu syslog.

Pausado até novo aviso.
fonte
2
Obrigado pela sua resposta. Esta solução é ótima. Tecnicamente, resolve meu problema, mas parece que há muita preparação da infraestrutura necessária para fazer esse trabalho. Não é portátil. Penso que sua solução seria idealmente inserida em uma distribuição Linux (não sei por que não!). Sua resposta inspirou minha solução definitiva, que também publiquei como resposta. Obrigado novamente!
Christopher Parker
O que fez você escolher o local3, em comparação com qualquer outra instalação entre 0 e 7?
Christopher Parker
3
@ Christopher: Um lançamento de dados é sempre o melhor método. Sério, no entanto, por exemplo, não importava e essa é a chave em que meu dedo pousou. Além disso, eu não possuo nenhum dado de oito lados.
Pausado até novo aviso.
@ Dennis: Entendi, obrigado. Coincidentemente, local3 é a instalação local que aparece em man logger.
22410 Christopher Christopher
A $filevariável contém o caminho completo ou apenas o nome do arquivo?
Andrew Savinykh
21

Eu realmente aprecio o esforço feito na resposta de Dennis Williamson . Eu queria aceitá-lo como resposta a esta pergunta, pois é elegante e simples, no entanto:

  • Por fim, senti que eram necessárias muitas etapas para configurar.
  • Requer acesso root.

Eu acho que sua solução seria ótima como um recurso pronto para uso em uma distribuição Linux.

Dito isto, escrevi meu próprio roteiro para realizar mais ou menos a mesma coisa que a solução de Dennis. Não requer etapas extras de configuração e não requer acesso root.

#!/bin/bash

if [[ $# -eq 0 ]]; then
    echo "Schedules a command to be run after the next reboot."
    echo "Usage: $(basename $0) <command>"
    echo "       $(basename $0) -p <path> <command>"
    echo "       $(basename $0) -r <command>"
else
    REMOVE=0
    COMMAND=${!#}
    SCRIPTPATH=$PATH

    while getopts ":r:p:" optionName; do
        case "$optionName" in
            r) REMOVE=1; COMMAND=$OPTARG;;
            p) SCRIPTPATH=$OPTARG;;
        esac
    done

    SCRIPT="${HOME}/.$(basename $0)_$(echo $COMMAND | sed 's/[^a-zA-Z0-9_]/_/g')"

    if [[ ! -f $SCRIPT ]]; then
        echo "PATH=$SCRIPTPATH" >> $SCRIPT
        echo "cd $(pwd)"        >> $SCRIPT
        echo "logger -t $(basename $0) -p local3.info \"COMMAND=$COMMAND ; USER=\$(whoami) ($(logname)) ; PWD=$(pwd) ; PATH=\$PATH\"" >> $SCRIPT
        echo "$COMMAND | logger -t $(basename $0) -p local3.info" >> $SCRIPT
        echo "$0 -r \"$(echo $COMMAND | sed 's/\"/\\\"/g')\""     >> $SCRIPT
        chmod +x $SCRIPT
    fi

    CRONTAB="${HOME}/.$(basename $0)_temp_crontab_$RANDOM"
    ENTRY="@reboot $SCRIPT"

    echo "$(crontab -l 2>/dev/null)" | grep -v "$ENTRY" | grep -v "^# DO NOT EDIT THIS FILE - edit the master and reinstall.$" | grep -v "^# ([^ ]* installed on [^)]*)$" | grep -v "^# (Cron version [^$]*\$[^$]*\$)$" > $CRONTAB

    if [[ $REMOVE -eq 0 ]]; then
        echo "$ENTRY" >> $CRONTAB
    fi

    crontab $CRONTAB
    rm $CRONTAB

    if [[ $REMOVE -ne 0 ]]; then
        rm $SCRIPT
    fi
fi

Salve esse script (por exemplo: runonce), chmod +xe execute:

$ runonce foo
$ runonce "echo \"I'm up. I swear I'll never email you again.\" | mail -s \"Server's Up\" $(whoami)"

No caso de um erro de digitação, você pode remover um comando da fila de runonce com o sinalizador -r:

$ runonce fop
$ runonce -r fop
$ runonce foo

O uso do sudo funciona da maneira que você esperaria. Útil para iniciar um servidor apenas uma vez após a próxima reinicialização.

myuser@myhost:/home/myuser$ sudo runonce foo
myuser@myhost:/home/myuser$ sudo crontab -l
# DO NOT EDIT THIS FILE - edit the master and reinstall.
# (/root/.runonce_temp_crontab_10478 installed on Wed Jun  9 16:56:00 2010)
# (Cron version V5.0 -- $Id: crontab.c,v 1.12 2004/01/23 18:56:42 vixie Exp $)
@reboot /root/.runonce_foo
myuser@myhost:/home/myuser$ sudo cat /root/.runonce_foo
PATH=/usr/sbin:/bin:/usr/bin:/sbin
cd /home/myuser
foo
/home/myuser/bin/runonce -r "foo"

Algumas notas:

  • Este script replica o ambiente (PATH, diretório de trabalho, usuário) em que foi chamado.
  • Ele foi projetado para adiar basicamente a execução de um comando, como seria executado "aqui e agora" até depois da próxima sequência de inicialização.
Christopher Parker
fonte
Seu script parece realmente útil. Uma coisa a notar é que ela destrói os comentários destrutivamente do crontab.
Pausado até novo aviso.
@ Dennis: Obrigado. Originalmente, eu não tinha aquela chamada grep extra lá, mas todos os comentários estavam se acumulando; três para cada vez que eu rodava o script. Acho que vou mudar o script para sempre remover as linhas de comentários que se parecem com esses três comentários gerados automaticamente.
Christopher Parker
@ Dennis: Feito. Os padrões provavelmente poderiam ser melhores, mas funciona para mim.
Christopher Parker
@Dennis: Na verdade, com base no crontab.c, acho que meus padrões estão bem. (Pesquise "NÃO EDITAR ESTE ARQUIVO" em opensource.apple.com/source/cron/cron-35/crontab/crontab.c. )
Christopher Parker
5

Criar, por exemplo /root/runonce.sh:

#!/bin/bash
#your command here
sed -i '/runonce.sh/d' /etc/rc.local

Adicionar a /etc/rc.local:

/root/runonce.sh
kaffeslangen
fonte
Isso é pura genialidade. Não se esqueça de chmod + x no /root/runonce.sh. Isso é perfeito para a atualização do apt-get em máquinas azuis que travam, porque o walinuxagent bloqueia o dpkg
CarComp
Isso é muito hacky (embora também tenha sido o que eu vim a princípio).
iBug 1/07
2

Configure o script em /etc/rc5.d com um S99 e faça com que ele seja excluído após a execução.

mpez0
fonte
2

Você pode fazer isso com o atcomando, embora eu note que você não pode (pelo menos no RHEL 5, que eu testei) usar at @rebootou at reboot, mas você pode usá-lo at now + 2 minutese depois shutdown -r now.

Isso não requer que seu sistema demore mais de 2 minutos para ser executado.

Pode ser útil onde você deseja configurar 0, embora eu prefira que o comando 'runonce' seja um kit padrão.

Cameron Kerr
fonte
Lembre-se de que, se o seu sistema demorar mais de 2 minutos até atdser parado, seu comando poderá ser executado durante o desligamento. Isso pode ser evitado parando atdantes de eliminar o comando atd nowe, em seguida, reiniciar.
mleu
2

Eu acho que essa resposta é a mais elegante:

Coloque o script /etc/init.d/scripte exclua-o automaticamente com a última linha:rm $0

A menos que o script seja 100% à prova de falhas, provavelmente é aconselhável lidar com exceções para evitar um loop de erro fatal.

geotoria
fonte
2

Eu costumava chkconfigfazer meu sistema executar automaticamente um script uma vez após a inicialização e nunca mais. Se o seu sistema usa ckconfig (Fedora, RedHat, CentOs, etc), isso funcionará.

Primeiro o script:

#!/bin/bash
# chkconfig: 345 99 10
# description: This script is designed to run once and then never again.
#


##
# Beginning of your custom one-time commands
#

plymouth-set-default-theme charge -R
dracut -f

#
# End of your custom one-time commands
##


##
# This script will run once
# If you would like to run it again.  run 'chkconfig run-once on' then reboot.
#
chkconfig run-once off
chkconfig --del run-once
  1. Nomeie o script run-once
  2. Coloque o script em /etc/init.d/
  3. Ativar o script chkconfig run-once on
  4. reiniciar

Quando o sistema inicializar, seu script será executado uma e nunca mais.

Ou seja, nunca mais, a menos que você queira. Você sempre pode reativar o script com o chkconfig run-once oncomando

Eu gosto dessa solução porque coloca um e apenas um arquivo no sistema e porque o comando executar uma vez pode ser reemitido, se necessário.

shrewmouse
fonte
-1

nos sistemas redhat e debian, você pode fazer isso em /etc/rc.local, é um tipo de autoexec.bat.

natxo asenjo
fonte
7
Isso será executado em cada inicialização e não apenas na próxima.
Pausado até novo aviso.
1
meu mal, eu interpretei mal a pergunta. Obrigado pela correção
Natxo asenjo