指定した回数だけシステムを起動するたびにコマンドを実行すると、自動的に削除されます。

指定した回数だけシステムを起動するたびにコマンドを実行すると、自動的に削除されます。

Ubuntu Server 16.04(xenial)を使用しており、指定された数のシステムが起動してから一度コマンドを実行してから自動的に削除したいと思います。解決策は2段階で構成されているようです。

  1. にコマンドを追加します/etc/bash.bashrc

  2. x回実行した後削除されるようにbash.bashrc何とか

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

一般化する

主な問題は、スクリプトが実行された回数、つまり連続したスクリプト実行の間にどのように持続するかを追跡する方法を見つけることです。スクリプトが終了した後、環境変数はその値を保持しないため、環境変数を使用してこれを行うことはできません。

最も明確な方法は、この数字をファイルに保存することです。スクリプトはファイルから値を読み取った後、更新された値をファイルに書き戻すことができます。この数の情報を保存するために2番目のファイルを使用したくない場合は、スクリプトを独自に更新することができます(例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>=))rm -- "$0", i.e. you probably intended for you 上記のコードを実行しようとしたときに直面した最初の(明らかな)問題は、次のように条件式の後とifステートメントの本文の後にセミコロンがないことです。

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。しかし、countEqualsしかないので、0これは起こりません。

count解決策を試すときに発生する基本的な問題は、スクリプトの実行間で値を保持する方法に関する問題を解決できないことです。count各実行時にリセットします。0

スクリプトの反復間で値を保持する最も明白な方法は、ファイルから値を読み取り、更新された値をそのファイルに書き換えることです。したがって、最初のソリューションです。

自分を単一のファイルに制限するには、そのファイルの特別な行(プログラミング方法で識別できるように区別しやすい行)に値を格納して、本質的に同じことを実行してからスクリプトを作成できます。各行の値を更新するために、反復後にそれ自体を変更します。したがって、2番目の解決策です。


最小限の修正と修正で解決しようとする試み

特定のソリューションがスタンドアロン(自己修正)ファイルとして機能するように追加しようとしたため、以下は、動作に必要な変更を最小限に抑えるスクリプトの修正バージョンです。

#!/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また、再起動時にスクリプトを実行したいが、新しいシェルセッションを開くたびに実行できるファイルからスクリプトを呼び出すと言います。起動時にスクリプトを実行する方法はいくつかあります。そのほとんどは特定のオペレーティングシステムによって異なります。

詳細については、次の記事を確認してください。


最後の解決策

コメントで深い議論をした後、実際に欲しいのは、タスクリストの変更に関する通知を表示するスクリプトであることが明らかになりました。

再起動後に初めてログインしたときにタスクが表示されます。また、5回の再起動後にジョブが消えることを望みます。

私たちは代替ソリューションを考えました。新しいソリューションは、複数のユーザーが同時に作業できるマルチユーザーソリューションです。 2つのシステム全体のスクリプトと2つのユーザー固有のデータファイルを使用します。

  • ~/.taskscount:descriptionコロンで区切られたペアのリスト(各ジョブごとに1つ)を格納するユーザー固有のファイル。

  • ~/.reminder-flag 最後の実行以降にジョブ通知が表示されたかどうかを追跡するユーザー固有のステータスファイル。

  • /usr/local/bin/update-task-counts.sh.tasksすべての数を減らし、数が0のジョブを削除してファイルを更新するシェルスクリプト。

  • reminder-flag/usr/local/bin/print-tasks.shファイルを確認して更新し、すべてのジョブの説明を印刷するシェルスクリプト。

以下はサンプル~/.tasksファイルです。

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

このリストの最初のジョブは合計5回表示され、2番目のジョブは合計3回表示され、最後のジョブは1回のみ表示されます。

また、このファイルを読み取って更新するにはスクリプトが必要です。以下はこれを実行できるスクリプトです。

#!/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に追加してユーザーの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スクリプトを手動でテストするには、スクリプトを2回実行するだけです。

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:~$

これは可能です。

おすすめ記事