Linux:计划命令在重新引导后运行一次(等效于RunOnce)


26

我想安排在Linux机器上重新启动后运行的命令。我知道如何执行此操作,以便在每次重新启动后使用@rebootcrontab条目始终运行该命令,但是我只希望该命令运行一次。它运行后,应从要运行的命令队列中删除。我实质上是在Windows世界中寻找与RunOnce等效的Linux 。

如果很重要:

$ 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

Answers:


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.dchown并进行chmod +x适当的设置。运行之后,您会发现它已移至ran子目录,并且日期和时间附加在其名称之后。您的中还将有一个条目syslog


2
感谢您的回答。这个解决方案很棒。从技术上讲,它可以解决我的问题,但是似乎需要进行大量的基础结构准备工作才能完成这项工作。它不是便携式的。我认为您的解决方案理想情况下可以移植到Linux发行版中(我不确定为什么不是这样!)。您的答案激发了我的最终解决方案,我也将其发布为答案。再次感谢!
Christopher Parker 2010年

是什么让您选择local3,而不是0至7之间的其他任何设施?
Christopher Parker 2010年

3
@克里斯托弗:掷骰子总是最好的方法。严重的是,例如,这并不重要,这就是我的手指所要踩到的钥匙。此外,我没有任何八面模具。
暂停,直到另行通知。

@丹尼斯:明白了,谢谢。巧合的是,local3是出现在中的本地工具man logger
Christopher Parker 2010年

是否$file变量包含完整路径或只是文件名?
Andrew Savinykh

21

我真的很感谢Dennis Williamson的回答。我想接受它作为这个问题的答案,因为它既优雅又简单:

  • 我最终觉得它需要太多步骤来设置。
  • 它需要root访问。

我认为他的解决方案作为Linux发行版的即用型功能将非常有用。

话虽如此,我写了自己的脚本来完成与Dennis解决方案大致相同的任务。它不需要任何额外的设置步骤,也不需要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

保存此脚本(例如runoncechmod +x,然后运行:

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

如果出现错字,可以使用-r标志从runonce队列中删除命令:

$ 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调用,但是所有评论都在堆积;每次我运行脚本时都要三个。我想我将脚本更改为始终删除看起来像这三个自动生成的注释的注释行。
Christopher Parker 2010年

@丹尼斯:完成。模式可能会更好,但是对我有用。
Christopher Parker 2010年

@Dennis:实际上,基于crontab.c,我认为我的模式很好。(在opensource.apple.com/source/cron/cron/cron-35/crontab/crontab.c中搜索“请勿编辑此文件”
Christopher Parker,2010年

5

创建例如/root/runonce.sh

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

添加到/etc/rc.local

/root/runonce.sh

这是纯粹的天才。不要忘记使用chmod + x /root/runonce.sh。这对于在挂起的azure计算机上进行apt-get升级非常理想,因为walinuxagent会阻止dpkg
CarComp

这太过分了(尽管这也是我刚想到的)。
iBug

2

使用S99在/etc/rc5.d中设置脚本,并在运行后将其删除。


2

您可以使用at命令执行此操作,尽管我注意到您(至少在经过测试的RHEL 5上)不能使用at @rebootat reboot,但是可以使用at now + 2 minutesthen shutdown -r now

这并不需要您的系统运行超过2分钟的时间。

在您希望设置为0的情况下这可能会很有用,尽管我宁愿希望'runonce'命令是标准工具包。


请注意,如果系统atd停止之前要花费超过2分钟的时间,则您的命令可能会在关机期间运行。可以通过先停止atd命令atd now再停止然后重新启动来避免这种情况。
mleu

2

我认为这个答案是最优雅的:

将脚本放入/etc/init.d/script并最后一行自动删除:rm $0

除非脚本是100%防故障的,否则明智的做法是处理异常以避免致命的错误循环。


2

我曾经chkconfig让我的系统在启动后自动运行一次脚本,以后再也不会运行一次。如果您的系统使用ckconfig(Fedora,RedHat,CentO等),则可以使用。

首先是脚本:

#!/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命令。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.