Ubuntu Server 16.04(xenial)を使用しており、指定された数のシステムが起動してから一度コマンドを実行してから自動的に削除したいと思います。解決策は2段階で構成されているようです。
にコマンドを追加します
/etc/bash.bashrc
。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
これらの変更を行った後、スクリプトは実行されますが、何の効果もありません。理由を理解するために、各行を順番に見てみましょう。
echo='welcome'
この行は
echo
文字列を格納する変数を生成しますwelcome
。このコマンドは出力を生成しません。文字列を印刷するには、welcome
次のものを使用する必要があります。echo
注文する、環境変数ではありません名前付き例えば「エコ」echo welcome
。count='0'
この行は
count
値を格納する変数を生成します0
。これはcount
次のことを意味します。0
すべてスクリプトの繰り返し。((count+1))
この行は、変数に関連する算術式を評価します
count
。これはまったく影響しません。欲しいなら増加count変数を使用すると、同様の操作を実行できます((count++))
。また、値を正しく増やしてもスクリプトがcount
終了しても、この変更は保持されません。しかもした変更を維持するには、前の行()を上書きしますcount=0
。if ((count>=5)); then rm -- "$0"; fi
count
この行は、変数が.以上の場合はスクリプトファイルを削除します5
。しかし、count
Equalsしかないので、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つのユーザー固有のデータファイルを使用します。
~/.tasks
count: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:~$
これは可能です。
答え2
@igalがこれを正しく実行する方法を説明しました。オリジナルがうまくいかない理由を知りたいです。
最初に気付くべきことは、シェルと環境変数です各プロセスにローカル。 (環境変数は子プロセスによって継承されますが、子プロセスは独自の変数のコピーを取得し、親プロセスの変数を共有しません。)これは、Welcome.shスクリプトが実行されるたびにその変数がデフォルトでclean slate - itなどcount
最後に、スクリプトが実行されたときに何が設定されているかを把握する方法はありません。
スクリプトが実行されるたびに、定義されcount
た変数なしで起動されます。次に、を実行して作成し、count=0
0count
に設定します。その後、1に((count+1))
増加しますcount
。スクリプトが実行されるたびに最初に実行されたと思います。。
実行間で実行回数を維持するには、メモリ常駐変数ではなく永続ストア(ディスクなど)に保存する必要があり、特にプロセス固有の変数ではない場所に保存する必要があります。別々のファイルに保存するのが最善の方法ですが、スクリプト自体に保存して毎回スクリプト自体を編集することも可能です。たとえば、コマンドは、thenなどcount=0
で編集されます。 @igalの答えは両方の可能性をカバーしています。count=1
count=2
しかし、元のスクリプトには他の問題があります。まず、値をif count='5'
テストする代わりにcount
セット5に進みます。比較するには、if [ "$count" -ge 5 ]
orif [[ "$count" -ge 5 ]]
などが必要ですif ((count>=5))
。バラよりバッシュFAQ #31に関する[ ]
情報[[ ]]
と算術式の BashFAQのための(( ))
。
第二に、私はあなたのシステムがどのように設定されているのかわかりませんが、起動するたびに/etc/bash.bashrcを実行したくありません。 Linuxでは、すべての対話型シェルの起動ファイルです。つまり、新しい対話型シェルが開かれるたびに実行されることを意味します。呼び出すのに適した場所を見つけます(システム/ディストリビューション/使用している項目によって異なります)。
第三に、スクリプトが自分で削除された後に/opt/welcome.sh
エラーが発生します。[ -f /opt/welcome.sh ] && /opt/welcome.sh
それを実行するには、次を使用します。存在する場合のみ。
答え3
コードが正しいbashコードであると仮定すると、コードが機能しない理由があります。ある実行から次の実行まで「count」値を保存することはありません。スクリプトが実行されるたびに値count
がに設定されます'0'
。この問題を解決するには、次のいくつかの回避策を提案します。
sed
ステートメントが、、、...にcount='0'
なるように各実行ごとにスクリプト自体を更新(使用)するようにします。count='1'
count='2'
count='5'
- 一部の外部ファイルにカウントデータを保存する
- 3番目の回避策を追加できます。ファイル名に数を保持します。つまり、ファイル名を変更すると、実行ごとにファイル名をWelcome.5、welcome.4 ...からwelcome.0に変更しても閉じたままになります。
簡単に言えば、コードに問題があるのではなく、デザインに問題があるのです。
また、コードにはいくつかの問題があります(ただし、問題を解決するだけでは不十分です)。
echo='welcome'
何も表示せず、echo
変数を「Welcome」に設定します。たぶんあなたはそれを心から考えていますecho Welcome
。((count+1))
追加する1
と、count
どこにも保存されない新しい値が生成されます。私の考えでは、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