每指定系统启动次数执行一个命令,然后它会自动删除

每指定系统启动次数执行一个命令,然后它会自动删除

我使用 Ubuntu 服务器 16.04 (xenial),并希望在给定的系统启动次数后执行一次命令,然后将其自动删除。该解决方案似乎包括两个阶段:

  1. 将命令添加到/etc/bash.bashrc.

  2. 使其在bash.bashrc执行 x 次后被删除,不知何故

我已经通过在末尾添加bash.bashrc以下命令来完成解决方案的第一阶段:

echo "Welcome!"

在 Bash 中有没有办法做到这一点?

更新-这是我尝试过但失败的:

cat <<-"EOF" > /opt/bootmsg.sh
    #!/bin/bash
    echo welcome!
    count='0'
    ((count++))
    sed -i -e "s/^count='[01234]'$/count='${count}'/" "$0"
    if ((count>=5)); then rm -- "$0"; fi
EOF
chmod +x /opt/bootmsg.sh

# Add in the end of  bash.bashrc:
# [ -f /opt/bootmsg.sh ] && /opt/bootmsg.sh

如果您发现代码有什么问题,请发布一个答案,其中包含该代码的固定版本并解释我做错了什么。

答案1

概括

主要问题是找到一种方法来跟踪脚本已运行的次数 - 一种在脚本的连续执行之间持续存在的方法。您不能使用环境变量执行此操作,因为它们在脚本终止后不会保留其值。

最明显的方法是将这个数字存储在一个文件中;该脚本可以从文件中读取该值,然后将更新后的值写回到文件中。如果您想避免使用第二个文件来存储此计数信息,那么您可以让脚本自行更新(例如使用sed)。我给出了示例解决方案来说明这两种方法。

您的解决方案尝试尝试更新环境变量并使用它来跟踪状态,但环境变量在脚本执行之间不会持续存在,这就是您的解决方案失败的原因。

免责声明:我给出了一个单文件解决方案的示例,因为它是明确要求的,但我个人更喜欢不涉及自修改脚本的解决方案。一般来说,自修改脚本往往不太稳定,更难调试,也更难理解。


解决方案 1:使用文件来存储计数

这是一个使用文件来跟踪所需的剩余重新启动次数的解决方案:

#!/usr/bin/env bash
# /opt/welcome.sh

# Read number of remaining reboots from file
REBOOTS="$(head -1 /var/reboots)"

# If the number of reboots is positive then
# execute the command and decrement the count
if [[ "${REBOOTS}" -ge 0 ]]; then

    # Run some commands
    echo "Doing some stuff... ($(( ${REBOOTS} - 1 )) left)"
fi

# Decrement the reboot count
REBOOTS="$(( ${REBOOTS} - 1 ))"

# Update the file
echo "${REBOOTS}" > /var/reboots

# If we've run out of reboots then delete the files
if [[ ! "${REBOOTS}" -gt 0 ]]; then
    rm /var/reboots
    rm -- "$0"
fi

下面是这个特定脚本实际运行情况的示例:

user@host:~$ echo 3 > /var/reboots

user@host:~$ bash /opt/welcome.sh 
Doing some stuff... (2 left)

user@host:~$ bash /opt/welcome.sh 
Doing some stuff... (1 left)

user@host:~$ bash /opt/welcome.sh 
Doing some stuff... (0 left)

user@host:~$ bash /opt/welcome.sh 
bash: /opt/welcome.sh: No such file or directory

解决方案 2:使用自修改脚本

或者,您也可以尝试将 count 变量嵌入脚本本身并使用 更新它sed,例如:

#!/usr/bin/env bash
# /opt/welcome.sh

# Read number of remaining reboots from file
declare REBOOTS=3

# If the number of reboots is positive then
# execute the command and decrement the count
if [[ "${REBOOTS}" -ge 0 ]]; then

    # Run some commands
    echo "Doing some stuff... ($(( ${REBOOTS} - 1 )) left)"
fi

# Decrement the reboot count
REBOOTS="$(( ${REBOOTS} - 1 ))"

# Update the script
sed -i -e "s/^declare REBOOTS.*\$/declare REBOOTS=${REBOOTS}/" "$0"

# If we've run out of reboots then delete the script
if [[ ! "${REBOOTS}" -gt 0 ]]; then
    rm -- "$0"
fi

如果没有附加文件,这应该具有相同的效果。


失败的解决方案尝试分析

更新:您向您的问题添加了以下解决方案尝试:

cat <<-"WELCOME" > /opt/welcome.sh
    #!/bin/bash
    echo='welcome'
    count='0'
    ((count+1))

    if ((count>=5)) then rm -- "$0" fi
WELCOME
chmod +x /opt/welcome.sh

# Add in the end of  bash.bashrc:
# [ -f /opt/welcome.sh ] && /opt/welcome.sh

您问为什么这个解决方案不起作用。在我看来,您尝试运行的实际脚本是这样的:

#!/bin/bash

echo='welcome'
count='0'
((count+1))

if ((count>=5)) then rm -- "$0" fi

((count>=))当我尝试运行上面的代码时遇到的第一个(表面的)问题是,在条件表达式后面和 if` 语句主体后面缺少一个分号,rm -- "$0", i.e. you probably intended for you如下所示:

if ((count>=5)); then rm -- "$0"; fi

进行这些更改后,脚本将执行,但不会产生任何效果。要了解原因,让我们依次浏览每一行。

  1. echo='welcome'

    此行创建一个echo存储字符串的变量welcome。请注意,此命令不会产生任何输出。如果你想打印字符串welcome,那么你必须使用echo 命令,不是环境变量命名的“回声”,例如echo welcome

  2. count='0'

    此行创建一个count存储值的变量0。请注意,这意味着count将等于0每一个脚本的迭代。

  3. ((count+1))

    该行计算涉及变量的算术表达式count。请注意,这根本没有任何影响。如果你想增量count 变量,那么你会做类似的事情((count++))。另请注意,即使您正确增加了 的值count,一旦脚本终止,此更改也不会持续。此外,即使你做过使更改持续存在,它将被前一行 ( ) 覆盖count=0

  4. if ((count>=5)); then rm -- "$0"; fi

    count如果变量大于或等于,此行将删除脚本文件5。然而,由于count只会等于0,所以这种情况永远不会发生。

您的解决方案尝试的根本问题是,它没有解决如何count在脚本执行之间保留持久值的问题:在每次执行时count重置为。0

在脚本迭代之间保留值的最明显方法是从文件中读取该值,然后将更新后的值写回该文件 - 因此是我的第一个解决方案。

如果您想将自己限制在单个文件中,那么您可以通过将值存储在该文件中的特殊行(该行很容易区分,以便可以通过编程方式识别)上来执行基本相同的操作,然后让脚本每次迭代后修改自身以更新该行上的值 - 因此是我的第二个解决方案。


最小程度修改、更正的解决方案尝试

由于您已添加希望让您的特定解决方案尝试作为独立(自修改)文件工作,因此这里是脚本的修改版本,其中包含使其正常运行所需的尽可能少的更改:

#!/bin/bash
echo welcome
count='0'
((count++))
sed -i -e "s/^count='[01234]'$/count='${count}'/" "$0"
if ((count>=5)); then rm -- "$0"; fi

如果您将其保存到/opt/welcome.sh(如您的帖子中所示),那么您可以像这样测试它:

user@host:~$ bash /opt/welcome.sh
welcome

user@host:~$ bash /opt/welcome.sh
welcome

user@host:~$ bash /opt/welcome.sh
welcome

user@host:~$ bash /opt/welcome.sh
welcome

user@host:~$ bash /opt/welcome.sh
welcome

user@host:~$ bash /opt/welcome.sh
bash: /opt/welcome.sh: No such file or directory

补充评论

此外,您说您希望在重新启动时运行该脚本,但您从文件中调用它.bashrc,该文件可能会在您每次打开新的 shell 会话时运行。有许多不同的方法可以在启动时运行脚本 - 其中许多方法取决于您的特定操作系统。

有关更多信息,您可以查阅以下文档:


最终解决方案

经过评论中的深入讨论后,很明显您真正想要的是一个显示任务列表变化提醒的脚本。

您希望在重新启动后首次登录时显示任务。您还希望任务在 5 次重新启动后消失。

我们想出了一个替代解决方案。新的解决方案是多用户解决方案,可以同时为多个用户工作。它使用两个系统范围的脚本和两个每用户数据文件:

  • ~/.tasks 每个用户的文件,存储以冒号分隔的形式对的列表count:description- 每个任务一个。

  • ~/.reminder-flag 每个用户的状态文件,用于跟踪自上次启动以来是否显示任务提醒。

  • /usr/local/bin/update-task-counts.sh 一个 shell 脚本,.tasks通过递减所有计数并删除计数为 0 的任务来更新文件。

  • /usr/local/bin/print-tasks.sh 一个 shell 脚本,用于检查和更新reminder-flag文件并打印所有任务描述。

这是一个示例~/.tasks文件:

5:This is task one.
3:This is task two.
1:Yet another task.

此列表中的第一个任务应总共显示 5 次,第二个任务总共应显示 3 次,最后一个任务仅显示一次。

我们还需要一个读取和更新该文件的脚本。这是一个可以做到这一点的脚本:

#!/usr/bin/env bash

# /usr/local/bin/update-task-counts.sh

# Set the location of the task file
TASKFILE="${HOME}/.tasks"

# Set the location of the reminder flag file
REMINDFILE="${HOME}/.remind-tasks"

# Set a flag so that we know we need to print the task messages
echo 1 > "${REMINDFILE}"

# If there is no task file, then exit
if [[ ! -f "${TASKFILE}" ]]; then
    exit 0
fi

# Create a temporary file
TEMPFILE="$(mktemp)"

# Loop through the lines of the current tasks-file
while read line; do

    # Extract the description and the remaining count for each task
    COUNT="${line/:*/}"
    DESC="${line/*:/}"

    # Decrement the count
    ((COUNT--))

    # If the count is non-negative then add it to the temporary file
    if [[ "${COUNT}" -ge 0 ]]; then
        echo "${COUNT}:${DESC}" >> "${TEMPFILE}"
    fi
done < "${TASKFILE}" 

# Update the tasks file (by replacing it with the temporary file)
mv "${TEMPFILE}" "${TASKFILE}"

当您运行此脚本时,它将迭代任务文件的每一行,减少每个任务的计数,然后更新任务文件,使其仅包含具有正计数的任务。

然后我们需要一个脚本来打印任务列表中的任务:

#!/usr/bin/env bash

# /usr/local/bin/print-tasks.sh

# Set the location of the task file
TASKFILE="${HOME}/.tasks"

# Set the location of the reminder flag file
REMINDFILE="${HOME}/.remind-tasks"

# If there is no task file, then exit
if [[ ! -f "${TASKFILE}" ]]; then
    exit 0
fi

# If the reminder flag isn't set, then exit
FLAG="$(head -1 ${REMINDFILE})"
if [[ ! "${FLAG}" -eq 1 ]]; then
    exit
fi

# Loop through the lines of the current tasks-file
while read line; do

    # Extract the description for each task
    DESC="${line/*:/}"

    # Display the task description
    echo "${DESC}"

done < "${TASKFILE}"

# Update the flag file so we know not to display the task list multiple times
echo 0 > "${REMINDFILE}"

最后要做的事情是确保在适当的时间调用这两个脚本。为了让update-task-counts.sh脚本在重新启动时运行,我们可以从用户的 crontab 中调用它,即将以下行添加到您的 crontab 中(例如使用crontab -e):

@reboot /bin/bash /usr/local/bin/update-task-counts.sh

有关此 cron 技术的进一步讨论,请参阅以下帖子:

为了让print-tasks.sh脚本在用户第一次进入 shell 会话时运行,我们可以从用户的 bash 配置文件中调用它,即将以下行添加到~/.bash_profile

bash /usr/local/bin/print-tasks.sh

现在让我们使用示例文件运行这些脚本~/.tasks

5:This is task one.
3:This is task two.
1:Yet another task.

以下是我们如何在不运行的情况下启用提醒update-task-counts.sh

user@host:~$ echo 1 > ~/.reminder-flag

要手动测试print-task.sh脚本,我们只需运行它两次:

user@host:~$ bash /usr/local/bin/print-tasks.sh
This is task one.
This is task two.
Yet another task.

user@host:~$ bash /usr/local/bin/print-tasks.sh

user@host:~$

请注意,它仅在第一次调用时打印。为了手动测试print-task.sh和之间的交互update-task-counts.sh,我们将它们一起运行,例如:

如果我们使用此文件手动运行上述脚本,结果如​​下:

user@host:~$  bash update-task-counts.sh
user@host:~$  bash print-tasks.sh
This is task one.
This is task two.
Yet another task.

user@host:~$  bash update-task-counts.sh
user@host:~$  bash print-tasks.sh
This is task one.
This is task two.

user@host:~$  bash update-task-counts.sh
user@host:~$  bash print-tasks.sh
This is task one.
This is task two.

user@host:~$  bash update-task-counts.sh
user@host:~$  bash print-tasks.sh
This is task one.

user@host:~$  bash update-task-counts.sh
user@host:~$  bash print-tasks.sh
This is task one.

user@host:~$  bash update-task-counts.sh
user@host:~$  bash print-tasks.sh

user@host:~$

应该可以做到这一点。

答案2

@igal 已经解释了如何正确执行此操作;我想看看为什么原来的不起作用。

首先要认识到 shell 和环境变量是每个进程本地的。 (环境变量由子进程继承,但子进程获取自己的变量副本,它不共享父进程的变量。)这意味着每次运行welcome.sh脚本时,它的变量本质上都是干净的slate——它没有任何方法可以找出例如count脚本上次运行时设置的内容。

每次脚本运行时,它都会以没有count定义变量的方式开始。然后它执行count=0,创建它count并将其设置为 0。然后((count+1))递增count到 1。每次脚本运行时,它都会认为这是它第一次运行

为了使运行计数在运行之间保持不变,它必须存储在某个永久存储中(即磁盘上),而不是存储在内存驻留变量中,尤其不是存储在每个进程变量中。将其存储在单独的文件中是执行此操作的最佳方法,但也可以将其存储在脚本本身中,并让脚本每次编辑自身,因此例如命令count=0被编辑为count=1、 thencount=2等。@igal 的答案包括这两种可能性。

顺便说一句,您的原始脚本中还有一些其他问题。首先,if count='5'不测试 的值count,它它到 5。为了进行比较,您需要类似if [ "$count" -ge 5 ]orif [[ "$count" -ge 5 ]]或 的东西if ((count>=5))。看Bash 常见问题解答 #31有关[ ]和 的信息[[ ]],以及关于算术表达式的 BashFAQ为了(( ))

其次,我不熟悉您的系统是如何设置的,但我不希望 /etc/bash.bashrc 每次启动时运行一次。在 Linux 上,它是每个交互式 shell 的启动文件,这意味着每次打开新的交互式 shell 时都会运行它。找到一个更合适的地方来调用它(这取决于您使用的系统/发行版/任何内容)。

第三,脚本删除自身后,/opt/welcome.sh会产生错误。使用[ -f /opt/welcome.sh ] && /opt/welcome.sh来运行它仅当它存在时

答案3

假设代码是正确的 bash 代码,那么您的代码不起作用有一个很好的理由:从一次执行到另一次执行,没有任何内容保存“count”的值。每次运行脚本时, 的值count都会设置为'0'。我们向您建议了几种解决方案来解决此问题:

  • 让脚本在每次执行时自我更新(使用sed),以便语句count='0'变为count='1', count='2', ...count='5'
  • 将计数数据保存在某个外部文件中
  • 我可以添加第三个解决方案:将计数保留在文件名中(即,在每次执行时重命名文件:welcome.5、welcome.4...到welcome.0,它会保持关闭状态)。

简而言之,这不是你的代码的问题,而是你的设计的问题。

此外,您的代码有几个问题(但修复这些问题还不够):

  • echo='welcome'不显示任何内容,它只是将echo变量设置为“欢迎”。也许你的意思是echo Welcome
  • ((count+1))添加1count但这会创建一个新值,该值不会存储在任何地方。我想你的意思是count=$((count+1))((count+=1))
  • count=0效果一样好count='0'

最后的建议:拥有自毁代码是一个坏主意:当你测试它时,它第一次工作时它会自行删除。在您 100% 确定它可以工作并且您拥有工作版本的副本之前,只需将其重命名即可。

答案4

奥托德尔欢迎.sh

mkwelcome.sh脚本:

#!/bin/bash
WelcomeFile=./welcome.sh
cat <<-"EOWelcome" >$WelcomeFile
    #!/bin/bash
    echo Welcome
    count=0
    old=$count
    ((count++))
    sed "s/^count=$old/count=$count/" -i $0
    ((count>4))&&rm $0
    EOWelcome
chmod +x $WelcomeFile

注意:小心使用表格,行首不要有空格!

双引号确保变量不会被解释

那么你可以尝试:

./mkwelcome.sh 
./welcome.sh 
Welcome
./welcome.sh 
Welcome
./welcome.sh 
Welcome
./welcome.sh 
Welcome
./welcome.sh 
Welcome
./welcome.sh 
bash: ./welcome.sh: No such file or directory

然后添加到您的.bashrc

[ -f /path/welcome.sh ] && /path/welcome.sh

漂亮的变化

sed 's/^ \+/\t/' >mkwelcome.sh

#!/bin/bash
WelcomeFile=./welcome.sh
cat <<-"EOWelcome" >$WelcomeFile
    #!/bin/bash
    conWord=(first second third fourth fifth) 
    count=0
    echo Welcome to your ${conWord[count]} login!
    old=$count
    ((count++))
    sed "s/^count=$old/count=$count/" -i $0
    ((count>4))&&rm $0
    EOWelcome
chmod +x $WelcomeFile

Ctrl+d

./mkwelcome.sh 
[ -x ./welcome.sh ] && ./welcome.sh 
Welcome to your first login!
[ -x ./welcome.sh ] && ./welcome.sh 
Welcome to your second login!
[ -x ./welcome.sh ] && ./welcome.sh 
Welcome to your third login!
[ -x ./welcome.sh ] && ./welcome.sh 
Welcome to your fourth login!
[ -x ./welcome.sh ] && ./welcome.sh 
Welcome to your fifth login!
[ -x ./welcome.sh ] && ./welcome.sh 
[ -x ./welcome.sh ] && ./welcome.sh 

相关内容