Linux: el comando Schedule se ejecutará una vez después del reinicio (equivalente RunOnce)

26

Me gustaría programar un comando para que se ejecute después de reiniciar en una caja de Linux. Sé cómo hacer esto para que el comando se ejecute constantemente después de cada reinicio con una @rebootentrada crontab, sin embargo, solo quiero que el comando se ejecute una vez. Una vez que se ejecuta, debe eliminarse de la cola de comandos para ejecutar. Básicamente estoy buscando un equivalente de Linux para RunOnce en el mundo de Windows.

En caso de que 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

¿Hay una manera fácil y programable de hacer esto?

Christopher Parker
fuente
1
RunOnce es un artefacto de Windows resultante de problemas para completar la configuración antes de reiniciar. ¿Hay alguna razón por la que no pueda ejecutar su script antes de reiniciar? La solución anterior parece ser un clon razonable de RunOnce.
BillThor

Respuestas:

38

Cree una @rebootentrada en su crontab para ejecutar un script llamado /usr/local/bin/runonce.

Cree una estructura de directorio llamada /etc/local/runonce.d/ranusando mkdir -p.

Cree el script de la /usr/local/bin/runoncesiguiente manera:

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

Ahora coloque cualquier secuencia de comandos que desea ejecutar en el siguiente reinicio (sólo una vez) en el directorio /etc/local/runonce.dy chowny chmod +xde manera apropiada. Una vez que se haya ejecutado, lo encontrará movido al ransubdirectorio y la fecha y hora adjuntas a su nombre. También habrá una entrada en tu syslog.

Pausado hasta nuevo aviso.
fuente
2
Gracias por tu respuesta. Esta solución es genial. Técnicamente resuelve mi problema, sin embargo, parece que se necesita mucha preparación de infraestructura para que esto funcione. No es portátil Creo que su solución idealmente se incluiría en una distribución de Linux (¡no estoy seguro de por qué no lo es!). Su respuesta inspiró mi solución definitiva, que también publiqué como respuesta. ¡Gracias de nuevo!
Christopher Parker
¿Qué te hizo elegir local3, en comparación con cualquiera de las otras instalaciones entre 0 y 7?
Christopher Parker
3
@ Christopher: Una tirada de dados es siempre el mejor método. Sin embargo, en serio, por ejemplo, no importó y esa fue la clave en la que mi dedo aterrizó. Además, no tengo ningún dado de ocho lados.
Pausado hasta nuevo aviso.
@ Dennis: Entendido, gracias. Casualmente, local3 es la instalación local que aparece en man logger.
Christopher Parker
¿La $filevariable contiene la ruta completa o solo el nombre del archivo?
Andrew Savinykh
21

Realmente aprecio el esfuerzo realizado en la respuesta de Dennis Williamson . Sin embargo, quería aceptarlo como la respuesta a esta pregunta, ya que es elegante y simple:

  • Finalmente sentí que requería demasiados pasos para configurarlo.
  • Requiere acceso de root.

Creo que su solución sería excelente como una característica lista para usar de una distribución de Linux.

Dicho esto, escribí mi propio guión para lograr más o menos lo mismo que la solución de Dennis. No requiere pasos de configuración adicionales y no requiere acceso de 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

Guardar esta secuencia de comandos (por ejemplo: runonce), chmod +xy ejecuta:

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

En caso de error tipográfico, puede eliminar un comando de la cola de ejecución con el indicador -r:

$ runonce fop
$ runonce -r fop
$ runonce foo

Usar sudo funciona de la manera que esperarías que funcione. Útil para iniciar un servidor solo una vez después del próximo reinicio.

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"

Algunas notas:

  • Este script replica el entorno (RUTA, directorio de trabajo, usuario) en el que se invocó.
  • Está diseñado para diferir básicamente la ejecución de un comando, ya que se ejecutaría "aquí, ahora" hasta después de la siguiente secuencia de arranque.
Christopher Parker
fuente
Tu guión se ve muy útil. Una cosa a tener en cuenta es que elimina destructivamente los comentarios del crontab.
Pausado hasta nuevo aviso.
@ Dennis: Gracias. Originalmente no tenía esa llamada grep adicional allí, pero todos los comentarios se estaban acumulando; tres por cada vez que ejecuté el script. Creo que cambiaré el script para eliminar siempre las líneas de comentarios que se parecen a esos tres comentarios generados automáticamente.
Christopher Parker
@ Dennis: Listo. Los patrones probablemente podrían ser mejores, pero funciona para mí.
Christopher Parker
@Dennis: En realidad, basado en crontab.c, creo que mis patrones están bien. (Busque "NO EDITE ESTE ARCHIVO" en opensource.apple.com/source/cron/cron-35/crontab/crontab.c. )
Christopher Parker
5

Crear, por ejemplo /root/runonce.sh:

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

Añadir a /etc/rc.local:

/root/runonce.sh
kaffeslangen
fuente
Esto es puro genio. No olvides chmod + x /root/runonce.sh. Esto es perfecto para apt-get upgrade en las máquinas azules que cuelgan porque bloquea la walinuxagent dpkg
CarComp
Esto es demasiado hacky (aunque también es lo que se me ocurrió al principio).
iBug
2

Configure el script en /etc/rc5.d con un S99 y haga que se elimine después de ejecutarse.

mpez0
fuente
2

Puede hacer esto con el atcomando, aunque noto que no puede (al menos en RHEL 5, que probé) usar at @rebooto at reboot, pero puede usar at now + 2 minutesy luego shutdown -r now.

Esto no requiere que su sistema tarde más de 2 minutos en ejecutarse.

Puede ser útil donde desee la configuración 0, aunque prefiero que el comando 'runonce' sea un kit estándar.

Cameron Kerr
fuente
Tenga en cuenta que si su sistema tarda más de 2 minutos en atddetenerse, su comando puede ejecutarse durante el apagado. Esto podría evitarse deteniéndose atdantes de eliminar el comando con atd nowy luego reiniciando.
mleu
2

Creo que esta respuesta es la más elegante:

Coloque el script /etc/init.d/scripty elimínelo automáticamente con la última línea:rm $0

A menos que el script sea 100% a prueba de fallas, probablemente sea inteligente manejar excepciones para evitar un ciclo de error fatal.

geoteoria
fuente
2

Solía chkconfighacer que mi sistema ejecutara automáticamente un script una vez después del arranque y nunca más. Si su sistema usa ckconfig (Fedora, RedHat, CentOs, etc.) esto funcionará.

Primero el guión:

#!/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. Nombra el guión run-once
  2. Coloque el guión en /etc/init.d/
  3. Habilita el script chkconfig run-once on
  4. reiniciar

Cuando el sistema arranca, su script se ejecutará una vez y nunca más.

Es decir, nunca más a menos que lo desee. Siempre puede volver a habilitar el script con el chkconfig run-once oncomando.

Me gusta esta solución porque pone uno y solo un archivo en el sistema y porque el comando ejecutar una vez se puede volver a emitir si es necesario.

musaraña
fuente
-1

en los sistemas redhat y debian puedes hacerlo desde /etc/rc.local, es una especie de autoexec.bat.

natxo asenjo
fuente
77
Eso se ejecutará en cada arranque, no solo en el siguiente.
Pausado hasta nuevo aviso.
1
mal, leí mal la pregunta. Gracias por la corrección
natxo asenjo