rc.localですべてのコマンドが実行されないのはなぜですか?


35

次のrc.localスクリプトがあります。

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

sh /home/incero/startup_script.sh
/bin/chmod +x /script.sh
sh /script.sh

exit 0

最初の行では、startup_script.sh実際にscript.shファイルをダウンロード/script.shし、3行目で参照します。

残念ながら、スクリプトを実行可能にしたり、スクリプトを実行したりしていないようです。rc.local開始後に手動でファイルを実行すると、完全に機能します。起動時などにchmodを実行できませんか?

回答:


70

A Quick Fixまではすべてスキップできますが、必ずしも最良のオプションではありません。だから私はこのすべてを最初に読むことを勧めます。

rc.local エラーを許容しません。

rc.localエラーからインテリジェントに回復する方法を提供しません。コマンドが失敗すると、実行が停止します。最初の行#!/bin/sh -eにより、-eフラグで呼び出されたシェルで実行されます。-eフラグが(この場合には、スクリプトを作るものであるrc.localコマンドがその中に失敗した初めての実行を停止します)。

あなたはrc.localこのように振る舞います。コマンドが失敗した場合、他の起動コマンドが成功したことに依存している可能性のあるコマンドを続行したくありません。

したがって、いずれかのコマンドが失敗すると、後続のコマンドは実行されません。ここでの問題は、/script.sh実行されなかったことです(失敗したことではなく、以下を参照)。しかし、どれですか?

でした/bin/chmod +x /script.shか?

いや

chmodいつでも正常に動作します。を含むファイルシステム/binがマウントされていれば、を実行できます/bin/chmod。そして、/bin前に装着されてrc.local実行されます。

rootとして実行した場合、/bin/chmod失敗することはほとんどありません。操作対象のファイルが読み取り専用の場合は失敗し、そのファイルシステムがアクセス許可をサポートしていない場合は失敗する可能性があります。どちらもここにはありません。

ところで、sh -eあるだけならば、それは実際に問題になる理由chmodに失敗しました。インタープリターを明示的に呼び出してスクリプトファイルを実行する場合、そのファイルが実行可能とマークされているかどうかは関係ありません。/script.shファイルの実行可能ビットが問題になると言われた場合のみ。それが言うのでsh /script.sh、それは実行しません(もちろん、実行中にそれ/script.sh 自体呼び出す場合、実行可能でないことから失敗する可能性がありますが、それ自体を呼び出す可能性は低いです)。

それで失敗したのは何ですか?

sh /home/incero/startup_script.sh失敗しました。ほぼ間違いなく。

ダウンロードし/script.shたため、実行されたことがわかります。

(そうでなければ、それは場合には何らかの形で、それが実行しなかったことを確認することが重要であろう/binではありませんでしたPATH - rc.local必ずしも同じを持っていませんPATH。あなたはログインしているときに持っているかのようには/binしていなかったrc.localのパス、この必要であろうshとして実行する/bin/sh。それは実行しなかったので、/binであるPATH、あなたはに位置している他のコマンドを実行できることを意味し/bin、完全に自分の名前を修飾せずに、たとえば、あなただけ実行することができますchmodではなく/bin/chmod。しかし、あなたのスタイルに合わせてではrc.local、実行をsh提案するたびに、を除くすべてのコマンドに完全修飾名を使用しました。)

/bin/chmod +x /script.sh絶対/script.shに実行されなかった(または実行されたことがわかります)と確信できます。そして、私たちsh /script.shはどちらも実行されなかったことを知っています。

しかし、ダウンロードしました/script.sh。成功しました!どうして失敗するのでしょうか?

成功の2つの意味

コマンドが成功したと言ったときに人が意味する可能性のある2つの異なることがあります。

  1. あなたがやりたいことをやりました。
  2. 成功したと報告されました。

そしてそれは失敗のためです。コマンドが失敗したと人が言うとき、それは次のことを意味します。

  1. 望んでいたことをしませんでした。
  2. 失敗したと報告されました。

sh -eなどrc.localで実行されるスクリプトは、コマンドが失敗したことを初めて報告したときに実行を停止ます。コマンドが実際に行ったことに違いはありません。

あなたが望むことをするstartup_script.shときに失敗を報告するつもりでない限り、これはのバグですstartup_script.sh

  • いくつかのバグにより、スクリプトが望んでいることを実行できません。それらはプログラマが副作用と呼ぶものに影響します。
  • また、いくつかのバグにより、スクリプトが成功したかどうかを正しく報告できません。これらは、プログラマがその戻り値(この場合は終了ステータス)を呼び出すものに影響します。

失敗したと報告された場合を除きstartup_script.sh、必要なすべてのことを実行した可能性が最も高いです。

成功または失敗の報告方法

スクリプトは、ゼロ個以上のコマンドのリストです。各コマンドには終了ステータスがあります。スクリプトの実際の実行に障害がないと仮定すると(たとえば、インタープリターが実行中にスクリプトの次の行を読み取れなかった場合)、スクリプトの終了ステータスは次のとおりです。

  • 0 (成功)スクリプトが空白の場合(つまり、コマンドがなかった場合)。
  • N、スクリプトは、コマンドの結果として終了している場合、一部の終了コードです。exit NN
  • それ以外の場合は、スクリプトで実行された最後のコマンドの終了コード。

実行可能ファイルは、実行されると、それ自身の終了コードを報告します。スクリプト用だけではありません。(技術的には、スクリプトからの終了コードは、スクリプトを実行するシェルによって返される終了コードです。)

たとえば、Cプログラムがexit(0);、またはreturn 0;そのmain()関数で終わる場合、コード0はオペレーティングシステムに渡され、呼び出しプロセス(たとえば、プログラムが実行されたシェルなど)に提供されます。

0プログラムが成功したことを意味します。1つおきの数字は、失敗したことを意味します。(このように、異なる数値は、プログラムが失敗したさまざまな理由を指す場合があります。)

失敗するコマンド

場合によっては、失敗することを意図してプログラムを実行します。これらの状況では、プログラムが失敗を報告するのはバグではありませんが、失敗は成功と考えるかもしれません。たとえば、rm削除されたことを確認するためだけに、既に存在しないと思われるファイルで使用する場合があります。

このようなことはおそらくstartup_script.sh、実行を停止する直前にで発生しています。実行するための最後のコマンドスクリプトではおそらく失敗を報告している(たとえその「失敗」完全に罰金、あるいは必要な場合があります)スクリプトレポートの失敗を行い、。

失敗するテスト

コマンドの特別な種類の1つはtestです。これは、副作用ではなく戻り値に対してコマンドを実行することを意味します。つまり、テストとは、終了ステータスを調査(および実行)できるように実行されるコマンドです。

たとえば、4が5に等しいかどうかを忘れるとします。幸いなことに、シェルスクリプトを知っています。

if [ 4 -eq 5 ]; then
    echo "Yeah, they're totally the same."
fi

ここでは、[ -eq 5 ]結局4≠5であるため、テストは失敗します。それは、テストが正しく実行されなかったことを意味しません。やった 4 = 5かどうかを確認し、そうであれば成功を報告し、そうでなければ失敗を報告するのが仕事でした。

シェルスクリプトでは、成功はtrueを意味し、失敗はfalseを意味することもあります

にもかかわらず、echo文が実行されることはありません、if全体としてブロックは、リターンの成功を行います。

しかし、私はそれを短く書いたと仮定します:

[ 4 -eq 5 ] && echo "Yeah, they're totally the same."

これは一般的な略記法です。&&あるブール オペレータ。&&で構成されて表現、&&両側が真(成功)を返していない限りいずれかの側の声明で、偽(失敗)を返します。ただ、通常のようにして

「デレクはモールに行って蝶のことを考えた?」と誰かが尋ねたら。デレクがモールに行かなかったことを知っているなら、彼が蝶のことを考えているかどうかを気にする必要はありません。

同様に、左側のコマンドが&&失敗した場合(false)、&&式全体がすぐに失敗します(false)。右側のステートメント&&は実行されません。

ここで、[ 4 -eq 5 ]実行します。「失敗」(falseを返す)。したがって、&&式全体が失敗します。echo "Yeah, they're totally the same."決して実行されません。すべてが正常に動作しましたが、このコマンドは失敗を報告します(if上記の同等の条件が成功を報告した場合でも)。

それがスクリプト内の最後のステートメントである場合(およびスクリプトはその前のあるポイントで終了するのではなく、それに到達した場合)、スクリプト全体が失敗を報告ます。

これ以外にも多くのテストがあります。たとえば、||(「または」)を使用したテストがあります。ただし、上記の例はテストとは何かを説明するのに十分であり、ドキュメントを効果的に使用して特定のステートメント/コマンドがテストかどうかを判断できるようにする必要があります。

shvs. sh -e、再訪

以来ライン(も参照この質問のトップに)持っている、それがコマンドで起動されたかのように、オペレーティングシステムは、スクリプトを実行します:#!/etc/rc.localsh -e

sh -e /etc/rc.local

対照的に、などの他のスクリプトはフラグなしstartup_script.shで実行されます。-e

sh /home/incero/startup_script.sh

その結果、それらのコマンドが失敗を報告しても、それらは実行し続けます。

これは正常で良いことです。他のほとんどのスクリプト(ほとんどのスクリプトを含む)rc.localで起動する必要があります。sh -erc.local

違いを覚えておいてください:

  • スクリプトはsh -e、コマンドに含まれるコマンドが最初に失敗を報告するときに、失敗を報告する出口で実行されます。

    スクリプトは、スクリプト内のすべてのコマンドが&&演算子で結合された単一の長いコマンドであるかのようです。

  • スクリプトを使用してsh(なしで-e)実行するスクリプトは、スクリプトを終了する(終了する)コマンドに到達するまで、またはスクリプトの最後まで実行し続けます。すべてのコマンドの成功または失敗は、本質的に無関係です(次のコマンドでチェックされない限り)。スクリプトは、最後に実行されたコマンドの終了ステータスで終了します。

結局のところ、スクリプトがそれほど失敗ではないことを理解するのに役立ちます

失敗したのに失敗したとスクリプトが考えないようにする方法はありますか?

実行が完了する直前に何が起こるかを確認します。

  1. 成功するはずのコマンドが失敗した場合は、その理由を見つけて問題を修正します。

  2. コマンドが失敗し、それが正しいことだった場合、その失敗ステータスが伝播しないようにします。

    • 失敗ステータスが伝播しないようにする1つの方法は、成功する別のコマンドを実行することです。/bin/true副作用はなく、成功を報告します(/bin/false何もせず、失敗もします)。

    • 別の方法は、スクリプトがによって終了することを確認することexit 0です。

      これは、必ずしもexit 0スクリプトの最後にあるのと同じではありません。たとえばif、スクリプトが内部で終了する-blockが存在する場合があります。

成功を報告する前に、スクリプトが失敗を報告する原因を知ることが最善です。本当に何らかの方法で失敗した場合(やりたいことを実行しないという意味で)、成功を報告することは望ましくありません。

クイックフィックス

startup_script.shexitで成功を報告できない場合は、rc.localそれを実行するコマンドを変更して、成功startup_script.shしなかったとしてもコマンドが成功を報告するようにすることができます。

現在、次のものがあります。

sh /home/incero/startup_script.sh

このコマンドには同じ副作用(つまり、実行の副作用startup_script.sh)がありますが、常に成功を報告します。

sh /home/incero/startup_script.sh || /bin/true

startup_script.sh失敗を報告する理由を知り、修正することをお勧めします。

クイックフィックスの仕組み

これは実際には||テスト、またはテストの例です。

あなたが私がゴミを取り出したのか、フクロウを磨いたのか尋ねるとします。ゴミを取り出したら、フクロウを磨いたかどうか覚えていなくても、「はい」と正直に言うことができます。

||実行の左側のコマンド。成功した場合(true)、右側を実行する必要はありません。したがって、startup_script.sh成功を報告すると、trueコマンドは実行されません。

ただし、[ゴミを出さなかった]startup_script.shという失敗が報告された/bin/true 場合[フクロウをブラッシングした場合]の結果が重要になります。

/bin/true常に成功を返します(またはtrueと呼ばれることもあります)。その結果、コマンド全体が成功し、次のコマンドをrc.local実行できます。

成功/失敗、真/偽、ゼロ/非ゼロに関する追加の注記。

これを無視してください。ただし、複数の言語でプログラムを作成する場合(シェルスクリプトだけでなく)には、この記事を読むことをお勧めします。

Cなどのプログラミング言語を使用するシェルスクリプト作成者にとって、またシェルスクリプトを使用するCプログラマにとって、次のことを発見することは大きな混乱のポイントです。

  • シェルスクリプトの場合:

    1. 戻り値は、成功および/またはtrue0意味します
    2. 失敗falseを0意味する以外の戻り値。
  • Cプログラミングの場合:

    1. 戻り値0false .. を意味します
    2. 以外の戻り値0trueを意味します
    3. 何が成功を意味し、何が失敗を意味するかについての単純なルールはありません。0成功を意味する場合もあれば、失敗を意味する場合もあれば、追加したばかりの2つの数値の合計がゼロであることを意味する場合もあります。一般的なプログラミングの戻り値は、さまざまな種類の情報を通知するために使用されます。
    4. プログラムの終了ステータスの数値は、シェルスクリプトの規則に従って成功または失敗を示す必要があります。つまり0、Cプログラムではfalseを意味しますが、0成功したことを報告する場合は、プログラムを終了コードとして返します(シェルはtrueとして解釈します)。

3
すごい答えだ!そこには、私が知らなかった多くの情報があります。最初のスクリプトの最後に0を返すと、残りのスクリプトが実行されます(chmod + xが実行されたことがわかります)。残念ながら、私のスクリプトの/ usr / bin / wgetは、script.shに入れるWebページの取得に失敗しているようです。このrc.localスクリプトを実行するまでにネットワークの準備が整っていないのではないかと推測しています。このコマンドをどこに置くと、ネットワークの起動時に実行され、起動時に自動的に実行されますか?
Programster

私は今のところsudo visudoを実行して次の行を追加して「ハッキング」しています:username ALL =(ALL)NOPASSWD:ALL running 'startup applications' program(what the CLI command is this?)、and startup_script to this、 startupscriptは、rootとして実行するために、sudoコマンドを使用してscript.shを事前に実行するようになりました(パスワードは不要になりました)。これは非常に安全で恐ろしいことは間違いありませんが、これはカスタムpxe-bootライブディストロディスク用です。
Programster

Stu2000を想定すると、@ incero(私はそれがユーザー名だと仮定)とにかく管理者であるとして任意のコマンドの実行を許可されているので、とrootではないこと、(自分のユーザーパスワードを入力することによって)スーパー安全でないとひどいです。他のソリューションが必要な場合は、これに関する新しい質問を投稿することをお勧めします。1つの問題は、コマンドが実行されrc.localなかった理由でした(また、コマンドを実行し続けた場合にどうなるかを確認しました)。これで、別の問題が発生します。rc.local実行する前にマシンをネットワーク接続するか、startup_script.sh後で実行するようにスケジュールする必要があります。
エリアケイガン

1
それ以来、私はこの問題に戻り、rc.localを編集しましたが、wgetが「機能」していませんでした。/usr/bin/sudo service networking restartwgetコマンドの前に起動スクリプトを追加すると、wgetが機能しました。
Programster

3
@EliahKagan素晴らしい網羅的な答え。私はほとんどより良いドキュメントにrc.local
出くわしませんでし

1

以下を変更することにより、デフォルトの動作をオーバーライドできます(私が使用するUnixのバリアント)。

#!/bin/sh -e

に:

#!/bin/sh

スクリプトの開始時。「-e」フラグは、shに最初のエラーで終了するよう指示します。そのフラグの長い名前は「errexit」であるため、元の行は次と同等です。

#!/bin/sh --errexit

はい、-eraspbian jessieの作品を削除します。
沖エリーリナルディ

これに-eは理由があります。私があなただったら私はそれをしないだろう。しかし、私はあなたの両親ではありません。
Wyatt8740

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.