Linux: commande Schedule à exécuter une fois après le redémarrage (équivalent RunOnce)

26

Je voudrais planifier une commande à exécuter après le redémarrage sur une boîte Linux. Je sais comment faire pour que la commande s'exécute de manière cohérente après chaque redémarrage avec une @rebootentrée crontab, mais je ne souhaite exécuter la commande qu'une seule fois. Après son exécution, il doit être supprimé de la file d'attente des commandes à exécuter. Je recherche essentiellement un équivalent Linux à RunOnce dans le monde Windows.

Au cas où cela compte:

$ 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-t-il un moyen simple et scriptable de le faire?

Christopher Parker
la source
1
RunOnce est un artefact de Windows résultant de problèmes de fin de configuration avant un redémarrage. Y a-t-il une raison pour laquelle vous ne pouvez pas exécuter votre script avant de redémarrer? La solution ci-dessus semble être un clone raisonnable de RunOnce.
BillThor

Réponses:

38

Créez une @rebootentrée dans votre crontab pour exécuter un script appelé /usr/local/bin/runonce.

Créez une structure de répertoires appelée /etc/local/runonce.d/ranusing mkdir -p.

Créez le script /usr/local/bin/runoncecomme suit:

#!/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

Maintenant, placez tout script que vous souhaitez exécuter au prochain redémarrage (une seule fois) dans le répertoire /etc/local/runonce.det chownet de chmod +xmanière appropriée. Une fois qu'il a été exécuté, vous le trouverez déplacé vers le ransous - répertoire et la date et l'heure sont ajoutées à son nom. Il y aura également une entrée dans votre syslog.

En pause jusqu'à nouvel ordre.
la source
2
Merci pour votre réponse. Cette solution est excellente. Cela résout techniquement mon problème, mais il semble qu'il y ait beaucoup de préparation de l'infrastructure requise pour faire ce travail. Ce n'est pas portable. Je pense que votre solution serait idéalement intégrée dans une distribution Linux (je ne sais pas pourquoi elle ne l'est pas!). Votre réponse a inspiré ma solution ultime, que j'ai également publiée comme réponse. Merci encore!
Christopher Parker
Qu'est-ce qui vous a poussé à choisir local3, par rapport aux autres installations entre 0 et 7?
Christopher Parker
3
@Christopher: Un lancer de dés est toujours la meilleure méthode. Sérieusement, cependant, pour un exemple, cela n'avait pas d'importance et c'est la clé sur laquelle mon doigt a atterri. De plus, je ne possède aucun dé à huit faces.
pause jusqu'à nouvel ordre.
@ Dennis: J'ai compris, merci. Par coïncidence, local3 est l'installation locale qui apparaît dans man logger.
Christopher Parker
La $filevariable contient-elle le chemin complet ou juste le nom du fichier?
Andrew Savinykh
21

J'apprécie vraiment l'effort fourni dans la réponse de Dennis Williamson . Je voulais l'accepter comme réponse à cette question, car elle est élégante et simple cependant:

  • J'ai finalement pensé qu'il fallait trop d'étapes à mettre en place.
  • Il nécessite un accès root.

Je pense que sa solution serait formidable en tant que fonctionnalité prête à l'emploi d'une distribution Linux.

Cela étant dit, j'ai écrit mon propre script pour accomplir plus ou moins la même chose que la solution de Dennis. Il ne nécessite aucune étape de configuration supplémentaire et ne nécessite pas d'accès 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

Enregistrer ce script (par exemple: runonce), chmod +xet exécution:

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

En cas de faute de frappe, vous pouvez supprimer une commande de la file d'attente runonce avec l'indicateur -r:

$ runonce fop
$ runonce -r fop
$ runonce foo

L'utilisation de sudo fonctionne comme vous vous y attendez. Utile pour démarrer un serveur une seule fois après le prochain redémarrage.

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"

Quelques notes:

  • Ce script réplique l'environnement (PATH, répertoire de travail, utilisateur) dans lequel il a été appelé.
  • Il est conçu pour différer essentiellement l'exécution d'une commande car elle serait exécutée "ici, maintenant" jusqu'à la prochaine séquence de démarrage.
Christopher Parker
la source
Votre script semble vraiment pratique. Une chose à noter est qu'il supprime de manière destructive les commentaires de la crontab.
pause jusqu'à nouvel ordre.
@Dennis: Merci. À l'origine, je n'avais pas cet appel grep supplémentaire, mais tous les commentaires s'accumulaient; trois pour chaque fois que j'ai exécuté le script. Je pense que je vais changer le script pour toujours supprimer les lignes de commentaires qui ressemblent à ces trois commentaires générés automatiquement.
Christopher Parker
@Dennis: Terminé. Les modèles pourraient probablement être meilleurs, mais cela fonctionne pour moi.
Christopher Parker
@Dennis: En fait, basé sur crontab.c, je pense que mes modèles sont très bien. (Recherchez «NE MODIFIEZ PAS CE FICHIER» sur opensource.apple.com/source/cron/cron-35/crontab/crontab.c. )
Christopher Parker
5

Créez par exemple /root/runonce.sh:

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

Ajouter à /etc/rc.local:

/root/runonce.sh
kaffeslangen
la source
C'est du pur génie. N'oubliez pas de chmod + x le /root/runonce.sh. C'est parfait pour une mise à niveau apt-get sur des machines azur qui se bloquent parce que walinuxagent bloque le dpkg
CarComp
C'est trop hacky (bien que ce soit aussi ce que j'ai trouvé au début).
iBug
2

Configurez le script dans /etc/rc5.d avec un S99 et faites-le supprimer lui-même après l'exécution.

mpez0
la source
2

Vous pouvez le faire avec la atcommande, bien que je remarque que vous ne pouvez pas (au moins sur RHEL 5, sur lequel j'ai testé) utiliser at @rebootou at reboot, mais vous pouvez utiliser at now + 2 minuteset ensuite shutdown -r now.

Cela ne nécessite pas que votre système prenne plus de 2 minutes pour s'exécuter.

Il peut être utile lorsque vous souhaitez configurer 0, bien que je préfère que la commande «runonce» soit un kit standard.

Cameron Kerr
la source
Sachez que si votre système prend plus de 2 minutes jusqu'à ce qu'il atdsoit arrêté, votre commande peut s'exécuter pendant l'arrêt. Cela pourrait être évité en s'arrêtant atdavant de supprimer la commande avec atd nowpuis en redémarrant.
mleu
2

Je pense que cette réponse est la plus élégante:

Placez le script /etc/init.d/scriptet supprimez-le automatiquement avec la dernière ligne:rm $0

À moins que le script ne soit à 100% infaillible, il est probablement judicieux de gérer les exceptions pour éviter une boucle d'erreur fatale.

géothéorie
la source
2

Avant, chkconfigmon système exécutait automatiquement un script une fois après le démarrage et plus jamais. Si votre système utilise ckconfig (Fedora, RedHat, CentOs, etc.), cela fonctionnera.

D'abord le 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. Nommez le script run-once
  2. Placez le script dans /etc/init.d/
  3. Activer le script chkconfig run-once on
  4. redémarrer

Lorsque le système démarre, votre script s'exécute une fois et jamais plus.

C'est-à-dire, plus jamais à moins que vous ne le vouliez. Vous pouvez toujours réactiver le script avec lechkconfig run-once on commande.

J'aime cette solution car elle place un et un seul fichier sur le système et parce que la commande run-once peut être réémise si nécessaire.

musaraigne
la source
-1

dans les systèmes redhat et debian, vous pouvez le faire depuis /etc/rc.local, c'est une sorte de autoexec.bat.

natxo asenjo
la source
7
Cela va être exécuté à chaque démarrage, pas seulement le suivant seulement.
pause jusqu'à nouvel ordre.
1
mon mauvais, j'ai mal lu la question. Merci pour la correction
natxo asenjo