web-dev-qa-db-ja.com

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

次の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ファイルを3行目で参照される/script.shにダウンロードします。

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

35
Programster

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

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

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

rc.localがこのように動作するようにします。コマンドが失敗した場合は、notを実行し、他のスタートアップコマンドが成功したことに依存している可能性のあるものを続行します。

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

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

番号。

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

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

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

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

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

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

(それ以外の場合は、何らかの理由で/binPATH --rc.localに同じPATHが含まれていない場合に実行することを確認することが重要です。 /binrc.localのパスにない場合、/bin/shとしてshを実行する必要があります。実行後、/binPATHにあるため、名前を完全修飾せずに/binにある他のコマンドを実行できます。たとえば、/bin/chmodではなく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のバグです。

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

startup_script.shがすべきことをすべて実行した可能性が最も高く、exceptは失敗したと報告しました。

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

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

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

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

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

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

失敗するコマンド

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

おそらく、実行が停止する直前に、startup_script.shでこのようなことが起こっています。スクリプトでを実行する最後のコマンドが失敗を報告している可能性があります(その「失敗」はまったく問題がないか、または必要な場合でも)。

失敗するテスト

特別な種類のコマンドの1つはa 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."

これは一般的な略記法です。 &&booleanand演算子です。両側にステートメントがある&&で構成される&&式は、両側がtrue(成功)を返さない限りfalse(失敗)を返します。通常のandと同じです。

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

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

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

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

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

sh vs. sh -e、再訪

#!この質問 も参照)/etc/rc.localの先頭にsh -eがあるため、オペレーティングシステムはスクリプトを実行しますコマンドで呼び出されたかのように:

sh -e /etc/rc.local

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

sh /home/incero/startup_script.sh

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

これは正常で良いことです。 rc.localsh -eおよび他のほとんどのスクリプト(rc.localによって実行されるほとんどのスクリプトを含む)で呼び出す必要があります。

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

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

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

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

結局のところそれほど失敗ではないことをスクリプトが理解できるようにする

失敗したのに失敗したとスクリプトが考えないようにする方法を教えてください。

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

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

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

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

    • もう1つは、exit 0によってスクリプトが終了することを確認することです。

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

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

クイックフィックス

startup_script.sh exitで成功を報告できない場合は、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が失敗を報告する理由を知って、それを修正することを覚えておいてください。

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

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

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

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

ただし、startup_script.shが失敗を報告した場合[ゴミ箱を取り出さなかった]、その後/bin/trueの結果[フクロウを磨いたら]が重要です。

/bin/trueは常に成功を返します(またはtrue、時々呼び出します)。その結果、コマンド全体が成功し、rc.localの次のコマンドを実行できます。

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

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

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

  • シェルスクリプト:

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

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

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

#!/bin/sh -e

に:

#!/bin/sh

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

#!/bin/sh --errexit
1
Bryce