1. 概要

優れたBashスクリプトは通常、含まれているコマンドを実行する前にいくつかのチェックを実行します。 たとえば、管理コマンドを実行するスクリプトは、スクリプトがrootユーザーによって呼び出されたか、sudoアクセスで呼び出されたかを確認する必要があります。 権限の少ないユーザーがスクリプトを呼び出す場合は、すぐにexitコマンドを呼び出して実行を停止する必要があります。 しかし、それは簡単ですか?

このチュートリアルでは、exitコマンドがBashスクリプトを終了する最良の方法ではない理由を説明します。これは確かに最も簡単な方法ですが、残念ながら最も安全な方法ではありません。 ここでは、他の方法を検討します。

2. Bashスクリプトを実行する2つの方法

Bashスクリプトを実行する一般的な方法の1つは、ファイルシステムパスのプレフィックスが付いたスクリプトファイル名を呼び出すことにより、シェルセッションからスクリプトを実行することです。たとえば、myscriptのファイル名でスクリプトを実行します。現在の作業ディレクトリにある.sh呼び出しの前に「./」を付けます。

$ ./myscript.sh

スクリプトを実行する別の方法があります。それは、スクリプト呼び出しの前にソースキーワードを付けることによってスクリプトをソーシングすることです。

$ source ./myscript.sh

または、sourceキーワードの代わりにドットを使用することもできます。

$ . ./myscript.sh

2.1. Bashスクリプトの実行とソーシングの違い

スクリプトを実行すると、Linuxは現在のシェルセッションの子プロセスを生成し、その中でmyscript.shを実行します。シェルプロセスは、が終了しない限り、スクリプトの子プロセスが終了するのを待っている間ブロックします。バックグラウンドで実行

スクリプトの実行とは異なり、スクリプトをソーシングしても子プロセスは生成されません。代わりに、スクリプト内のコマンドは、ソーシングが行われる現在のプロセスで直接実行されます。

いくつかの証拠を見てみましょう。

まず、psコマンドを呼び出すだけの簡単なスクリプトmyscript.shを作成します。

#!/bin/bash
ps -f

次に、実行する前に実行可能にします。

$ chmod +x myscript.sh
$ ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+   149   148  0 14:27 tty1     00:00:00 -bash
mlukman+   262   149  0 14:45 tty1     00:00:00 /bin/bash ./myscript.sh
mlukman+   263   262  0 14:45 tty1     00:00:00 ps -f

上記の例では、現在のシェルプロセスのPIDは149です。 script.shを実行すると、PIDが262のBash子プロセスが生成されました。 次に、そのプロセスは、psコマンドをPID263の子プロセスとして実行しました。

それでは、スクリプトを調達してみましょう。

$ source ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+   149   148  0 14:27 tty1     00:00:00 -bash
mlukman+   264   149  0 14:59 tty1     00:00:00 ps -f

ご覧のとおり、 ps コマンド呼び出しのプロセスは、現在のシェルプロセスの直接の子でした。 シェルセッションの中間のBash子プロセスはありませんでした。

2.2. 実行時のexitコマンドの動作と 調達する場合

次に、スクリプトに終了コマンドがある場合に何が起こるかを見てみましょう。

#!/bin/bash
ps -f
exit
echo We should not see this

スクリプトを実行すると、以前と同じ結果が得られます。

$ ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+    10     9  1 15:05 tty1     00:00:00 -bash
mlukman+    25    10  0 15:06 tty1     00:00:00 /bin/bash ./myscript.sh
mlukman+    26    25  0 15:06 tty1     00:00:00 ps -f

ただし、ソーシングの動作は以前とは大きく異なります。

シェルセッションは終了します。 スクリプトをソーシングすると、現在のプロセスで直接コマンドが実行されるため、スクリプトのexitコマンドは、シェルセッションである現在のプロセスを終了します。

その小さな実験から得られる教訓は、exitコマンドをBashスクリプト内で使用するのが常に安全であるとは限らないということです。 スクリプトを安全に終了するには、いくつかのトリックを使用する必要があります。

3. returnコマンド

ソーススクリプトを終了するには、exitコマンドの代わりにreturnコマンドが必要です

#!/bin/bash
ps -f
return
echo We should not see this

スクリプトを入手すると、psコマンドだけを使用したバージョンのスクリプトと同じ結果が得られます。

$ source ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+    10     9  0 15:05 tty1     00:00:00 -bash
mlukman+    28    10  0 21:09 tty1     00:00:00 ps -f

ただし、実行するとエラーがスローされます。

$ ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+    10     9  0 15:05 tty1     00:00:00 -bash
mlukman+    29    10  0 21:24 tty1     00:00:00 /bin/bash ./myscript.sh
mlukman+    30    29  0 21:24 tty1     00:00:00 ps -f
./myscript.sh: line 3: return: can only `return' from a function or sourced script
We should not see this

エラーメッセージに記載されているように、 return コマンドは、関数またはソーススクリプトでのみ許可され、スクリプトの実行時には許可されません。

3.1. リターンおよびエグジットコンボ

スクリプトを実行とソーシングの両方と互換性を持たせるには、returnコマンドとexitコマンドの両方を組み合わせる必要があります。

まず、 return コマンドを作成して、STDERRを / dev / null にパイプすることにより、エラーメッセージを出力しないようにします。

return 2> /dev/null

次に、exitコマンドを追加します。

return 2> /dev/null; exit

この行をmyscript.shに適用します。

#!/bin/bash
ps -f
return 2> /dev/null; exit
echo We should not see this

最後に、スクリプトの実行とソーシングの両方を試してみましょう。

$ ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+    10     9  0 15:05 tty1     00:00:00 -bash
mlukman+    35    10  0 21:31 tty1     00:00:00 /bin/bash ./myscript.sh
mlukman+    36    35  0 21:31 tty1     00:00:00 ps -f
$ source ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+    10     9  0 15:05 tty1     00:00:00 -bash
mlukman+    37    10  0 21:31 tty1     00:00:00 ps -f

出来上がり! スクリプトは、両方の呼び出し方法で意図したとおりに機能するようになりました。 しかし、私たちは終わりましたか? あまり。

3.2. 複雑なスクリプトとの非互換性

returnコマンドを使用してスクリプトを実行したときに表示されたエラーメッセージを覚えていますか?

./myscript.sh: line 3: return: can only `return' from a function or sourced script

興味深いことに、エラーメッセージには「機能」と記載されていました。 関数でreturn-and-exitコンボメソッドを使用するとどうなるか知りたくありませんか? return-and-exitコンボラインを関数に移動し、代わりにその関数を呼び出しましょう。

#!/bin/bash

exitscript () {
  return 2> /dev/null; exit
}

ps -f
exitscript
echo We should not see this

次に、スクリプトがまだ機能するかどうかを確認します。

$ ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+    10     9  0 15:05 tty1     00:00:00 -bash
mlukman+    40    10  0 21:43 tty1     00:00:00 /bin/bash ./myscript.sh
mlukman+    41    40  0 21:43 tty1     00:00:00 ps -f
We should not see this
$ source ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+    10     9  0 15:05 tty1     00:00:00 -bash
mlukman+    42    10  0 21:43 tty1     00:00:00 ps -f
We should not see this

残念ながら、「これは表示されないはずです」という行のエコーから明らかなように、関数内から呼び出された場合、return-and-exitコンボメソッドは機能しなくなります。

4. killコマンド

前のセクションで説明したリターンとエグジットのコンボトリックは、メソッドの実行とソーシングの両方で機能します。 ただし、関数で使用すると惨めに失敗します。 新しいトリックを見つける必要があります。

ここで試すトリックは少し怖いようです。 killコマンドです。

特に、スクリプトを強制終了させます。 まあ、そうではありません。 killコマンドを使用してそれ自体を中断させます

kill -SIGINT $$

SIGINT オプションは、 kill コマンドに、コマンドを実行している現在のプロセスである$$によって参照されるPIDに割り込み信号を送信するように指示します。 。 CTRL + C の動作と同様に、割り込みシグナルを送信すると、シェルセッションプロセスであるかBash子プロセスであるかに関係なく、現在のプロセスで実行中およびキューに入れられているものはすべて停止します。

それをスクリプトに実装して、return-and-exitコンボ行を置き換えましょう。

#!/bin/bash

exitscript () {
  kill -SIGINT $$
}

ps -f
exitscript
echo We should not see this

変更したスクリプトをテストしてみましょう。

$ ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+    10     9  0 Feb19 tty1     00:00:00 -bash
mlukman+   113    10  0 07:01 tty1     00:00:00 /bin/bash ./myscript.sh
mlukman+   114   113  0 07:01 tty1     00:00:00 ps -f

$ source ./myscript.sh
UID        PID  PPID  C STIME TTY          TIME CMD
mlukman+    10     9  0 Feb19 tty1     00:00:00 -bash
mlukman+   115    10  0 07:01 tty1     00:00:00 ps -f

追加の空の行は別として、スクリプトが機能するようになり、killコマンドが関数内からスクリプトを正常に終了することを示しています。 ただし、このメソッドを使用すると、スクリプトの実行が突然停止し、終了コードが正しく返されなくなるという副作用があります。したがって、このメソッドの使用は、内部から終了する必要がある場合にのみ制限する必要があります。関数であり、終了コードは必要ありません。

5. 結論

この記事では、Bashスクリプトの実行とソーシングの違いと、exitコマンドを使用してスクリプトを終了する方法を説明しました。 リターンとエグジットのコンボメソッドと割り込みメソッドの2つのメソッドを検討しました。 関数内から終了する必要がない限り、return-and-exitコンボメソッドを使用することをお勧めします。その場合、割り込みメソッドに頼る必要があります。