Linux: Запланировать выполнение команды один раз после перезагрузки (эквивалент RunOnce)

26

Я хотел бы запланировать команду для запуска после перезагрузки на компьютере с Linux. Я знаю, как это сделать, чтобы команда последовательно запускалась после каждой перезагрузки с @rebootзаписью crontab, однако я хочу, чтобы команда запускалась только один раз. После запуска он должен быть удален из очереди команд для запуска. По сути, я ищу Linux-эквивалент RunOnce в мире Windows.

В случае, если это имеет значение:

$ 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

Есть ли простой способ сделать это?

Кристофер Паркер
источник
1
RunOnce - это артефакт Windows, возникающий из-за проблем с завершением настройки перед перезагрузкой. Есть ли причина, по которой вы не можете запустить свой скрипт до перезагрузки? Представленное выше решение представляется разумным клоном RunOnce.
BillThor

Ответы:

38

Создайте @rebootзапись в вашем crontab для запуска скрипта с именем /usr/local/bin/runonce.

Создайте структуру каталогов /etc/local/runonce.d/ranс использованием mkdir -p.

Создайте скрипт /usr/local/bin/runonceследующим образом:

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

Теперь поместите любой скрипт , который вы хотите запустить при следующей перезагрузке (только один раз) в каталоге /etc/local/runonce.dи chownи chmod +xего соответствующим образом . После запуска вы обнаружите, что он перемещен в ranподкаталог, а дата и время добавлены к его имени. Там также будет запись в вашем syslog.

Приостановлено до дальнейшего уведомления.
источник
2
Спасибо за Ваш ответ. Это решение отлично. Технически это решает мою проблему, однако, похоже, что для этой работы требуется большая подготовка инфраструктуры. Это не портативный. Я думаю, что ваше решение в идеале должно быть встроено в дистрибутив Linux (я не уверен, почему это не так!). Ваш ответ вдохновил мое окончательное решение, которое я также опубликовал в качестве ответа. Еще раз спасибо!
Кристофер Паркер
Что заставило вас выбрать local3, в отличие от других объектов от 0 до 7?
Кристофер Паркер
3
@Christopher: бросок костей всегда лучший метод. Серьезно, хотя, к примеру, это не имело значения, и это ключ, на котором мой палец приземлился. Кроме того, мне не принадлежит ни один восьмисторонний кубик.
Приостановлено до дальнейшего уведомления.
@ Денис: Понял, спасибо. По совпадению, local3 - это местный объект, который появляется в man logger.
Кристофер Паркер
Имеет ли $fileсодержать переменную полный путь или только имя файла?
Андрей Савиных
21

Я действительно ценю усилия, приложенные к ответу Денниса Уильямсона . Я хотел принять это как ответ на этот вопрос, поскольку это элегантно и просто, однако:

  • В конечном итоге я почувствовал, что для его установки требуется слишком много шагов.
  • Требуется root-доступ.

Я думаю, что его решение было бы замечательно в качестве встроенной функции дистрибутива Linux.

При этом я написал свой собственный сценарий, чтобы выполнить более или менее то же самое, что и решение Денниса. Он не требует каких-либо дополнительных шагов настройки и не требует 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

Сохраните этот скрипт (например: runonce), chmod +xи запуск:

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

В случае опечатки вы можете удалить команду из очереди runonce с флагом -r:

$ runonce fop
$ runonce -r fop
$ runonce foo

Использование sudo работает так, как вы ожидаете. Полезно для запуска сервера только один раз после следующей перезагрузки.

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"

Некоторые заметки:

  • Этот сценарий копирует среду (PATH, рабочий каталог, пользователь), в которой он был вызван.
  • Он предназначен для того, чтобы в основном отложить выполнение команды, как если бы она выполнялась «прямо здесь, прямо сейчас» до следующей последовательности загрузки.
Кристофер Паркер
источник
Ваш сценарий выглядит действительно удобным. Стоит отметить, что он разрушительно удаляет комментарии из crontab.
Приостановлено до дальнейшего уведомления.
@ Денис: Спасибо. Изначально у меня не было этого дополнительного вызова grep, но все комментарии накапливались; три за каждый раз, когда я запускал сценарий. Я думаю, что я изменю скрипт, чтобы просто всегда удалять строки комментариев, которые выглядят как эти три автоматически сгенерированных комментария.
Кристофер Паркер
@ Денис: Готово. Шаблоны, вероятно, могли бы быть лучше, но это работает для меня.
Кристофер Паркер
@Dennis: На самом деле, основываясь на crontab.c, я думаю, что мои шаблоны просто хороши. (Ищите «НЕ РЕДАКТИРОВАТЬ ЭТОТ ФАЙЛ» на opensource.apple.com/source/cron/cron-35/crontab/crontab.c. )
Кристофер Паркер,
5

Создать например /root/runonce.sh:

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

Добавить к /etc/rc.local:

/root/runonce.sh
kaffeslangen
источник
Это чистый гений. Не забудьте chmod + x /root/runonce.sh. Это идеально подходит для обновления apt-get на лазурных машинах, которые зависают, потому что walinuxagent блокирует dpkg
CarComp
Это слишком глупо (хотя это также то, что я придумал сначала).
iBug
2

Установите скрипт в /etc/rc5.d с помощью S99 и после его запуска удалите его.

mpez0
источник
2

Вы можете сделать это с помощью atкоманды, хотя я заметил, что вы не можете (по крайней мере на RHEL 5, на которой я тестировал) использовать at @rebootили at reboot, но вы можете использовать at now + 2 minutesи тогда shutdown -r now.

Это не требует, чтобы ваша система работала более 2 минут.

Это может быть полезно, когда вы хотите установить 0, хотя я бы предпочел, чтобы команда runonce была стандартным комплектом.

Кэмерон Керр
источник
Помните, что если ваша система длится более 2 минут до atdостановки, ваша команда может выполняться во время выключения. Этого можно избежать, остановившись atdперед выполнением команды с помощью, atd nowа затем перезагрузившись.
Млеу
2

Я думаю, что этот ответ является самым элегантным:

Поместите скрипт в /etc/init.d/scriptи удалите его с последней строки:rm $0

Если сценарий не на 100% безошибочен, возможно, целесообразно обрабатывать исключения, чтобы избежать фатальной ошибки.

geotheory
источник
2

Раньше chkconfigмоя система автоматически запускала скрипт один раз после загрузки и никогда больше. Если ваша система использует ckconfig (Fedora, RedHat, CentOs и т. Д.), Это будет работать.

Сначала скрипт:

#!/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. Назовите сценарий run-once
  2. Поместите скрипт в /etc/init.d/
  3. Включить скрипт chkconfig run-once on
  4. перезагружать

При загрузке системы ваш скрипт будет запускаться один раз и никогда больше.

То есть никогда больше, если вы этого не хотите. Вы всегда можете повторно включить скрипт с помощью chkconfig run-once onкоманды.

Мне нравится это решение, потому что оно помещает один и только один файл в систему, и потому что команда run-Once может быть повторно введена при необходимости.

землеройка
источник
-1

в системах redhat и debian вы можете сделать это из /etc/rc.local, это своего рода autoexec.bat.

natxo asenjo
источник
7
Это будет выполняться при каждой загрузке, а не только при следующей.
Приостановлено до дальнейшего уведомления.
1
мой плохой, я неправильно понял вопрос. Спасибо за исправление
natxo asenjo