trap によるエラー処理

ShellBeginner
オンラインで実践に進む

はじめに

スクリプトの実行中に、一時ファイルを作成したり、バックグラウンドプロセスを開始したりすることがあります。ユーザーが Ctrl+C などでスクリプトを中断すると、一時ファイルが残り、システム上に不要なファイルが蓄積することがあります。

これを防ぐには、スクリプトが停止されたことを検知し、必要なクリーンアップ処理を実行する仕組みが必要です。

Linux では、trap コマンドを使って中断の シグナル や終了イベントを捕捉します。シグナルは、オペレーティングシステムがプロセスと通信する方法の 1 つです。たとえば、Ctrl+C を押すと通常は SIGINT が送信され、プロセスに停止を要求します。

これにより、スクリプトの終了前に後片付けを行う「終了時処理」を定義できます。

この実験では、次の操作を行います。

  1. 一時ファイルを作成するスクリプトを作成します。
  2. スクリプトを中断して、残ったファイルを確認します。
  3. trap を設定し、終了時に一時ファイルを自動削除します。

長時間実行されるプロセスを作成する

まず、長時間実行されるタスクをシミュレートするスクリプトを作成します。このスクリプトは一時ファイルを作成した後、無期限にスリープします。

長時間実行されるスクリプトは、システム管理でよく使われます。ログの監視、入力待ち、サービスのポーリング、時間のかかるジョブの処理などがその例です。スクリプトの実行時間が長いほど、途中で中断されたときのクリーンアップが重要になります。

プロジェクトディレクトリで long_process.sh を作成して開きます。

cd ~/project
nano long_process.sh

次のコードを入力します。

#!/bin/bash

## Create a temporary file
touch temp_data.txt
echo "Temporary file 'temp_data.txt' created."

## Fall asleep (Simulate a long job)
echo "Process is running. Press Ctrl+C to stop..."
while true; do
  sleep 1
done

Ctrl+O、Enter の順に押して保存し、Ctrl+X を押して終了します。

スクリプトに実行権限を付与します。

chmod +x long_process.sh

問題点:このスクリプトを実行して Ctrl+C で停止すると、temp_data.txt はディスク上に残ります。繰り返し実行すると不要なファイルが蓄積し、ディスク容量を消費するため、これは望ましくありません。

一時ファイルは典型的な例ですが、同じ問題はロックファイル、ソケット、マウントポイント、バックグラウンドの補助プロセスでも発生します。クリーンアップの目的は、システムを予測可能な状態に戻すことです。

クリーンアップ関数を定義する

適切にクリーンアップを行うには、クリーンアップに必要な処理を 1 つの専用関数にまとめるのがよい方法です。

クリーンアップ処理を別の関数に分けると、スクリプトの構成が整理されます。また、複数の終了経路で同じ処理を使い回せるため、同じコードを複数箇所に記述せずに済みます。

もう一度スクリプトを開きます。

nano long_process.sh

スクリプトの先頭に cleanup 関数を追加します。

#!/bin/bash

## Define the cleanup function
cleanup() {
  echo ""
  echo "Cleaning up temporary files..."
  rm -f temp_data.txt
  echo "Cleanup complete."
}

## Create a temporary file
touch temp_data.txt
echo "Temporary file 'temp_data.txt' created."

## Simulate a long-running process
echo "Process is running. Press Ctrl+C to stop..."
while true; do
  sleep 1
done

保存して終了します。

この時点では関数を定義しただけで、スクリプトに いつ 関数を呼び出すかはまだ指定していません。

シグナルを捕捉する trap を設定する

ここでは trap コマンドを使い、Bash に「Ctrl+C などの割り込みシグナルを受け取ったら、cleanup 関数を実行する」と指示します。

これは、シェルスクリプトがテキスト入力だけでなく、オペレーティングシステムのイベントにも反応することを示す分かりやすい例です。シグナルは、システムまたはユーザーからプロセスへ送られる非同期メッセージです。trap を使うと、スクリプトでそのシグナルに意図した処理を行えます。

スクリプトを開きます。

nano long_process.sh

関数定義の下、メイン処理の開始前に trap コマンドを追加します。

#!/bin/bash

cleanup() {
  echo ""
  echo "Cleaning up temporary files..."
  rm -f temp_data.txt
  echo "Cleanup complete."
  exit
}

## Register the trap
## SIGINT = Ctrl+C
## EXIT = Any exit (success or failure)
trap cleanup SIGINT EXIT

## The rest of the script...
touch temp_data.txt
echo "Temporary file 'temp_data.txt' created."

echo "Process is running. Press Ctrl+C to stop..."
while true; do
  sleep 1
done

trap arguments の意味:

  • cleanup:実行するコマンドまたは関数です。
  • SIGINT:Ctrl+C を押したときに送信されるシグナルです。
  • EXIT:スクリプトがどのような理由で終了してもトリガーされる擬似シグナルです。

SIGINT と EXIT の両方を使うと、クリーンアップの信頼性が高まります。SIGINT は一般的なキーボード操作による中断に対応し、EXIT は通常の終了や多くのエラーケースに対応する、より一般的な安全策になります。

保存して終了します。

クリーンアップを確認する

ここでは、設定した仕組みが正しく動作することを確認します。

スクリプトを実行します。

./long_process.sh

スクリプトによって temp_data.txt が作成され、実行が開始されます。

次に、Ctrl+C を押してスクリプトを中断します。

次のような出力が表示されます。

^C
Cleaning up temporary files...
Cleanup complete.

最後に、ファイルが削除されたことを確認します。

ls temp_data.txt

次のようなエラーが返れば、ファイルは削除されています。

ls: cannot access 'temp_data.txt': No such file or directory

trap コマンドが停止シグナルを正常に捕捉し、スクリプトの終了前にクリーンアップ処理を実行したことが分かります。

これは、プロフェッショナルなスクリプトで重要な習慣です。スクリプトが一時的な状態を作成する場合は、その状態を削除する方法も定義する必要があります。trap は、そのために Bash が提供する主要なツールの 1 つです。

まとめ

この実験では、Trap を使って堅牢なスクリプトを作成する方法を学びました。

  1. シグナル:Ctrl+C がプロセスに SIGINT シグナルを送信することを理解しました。
  2. Trap:trap を使ってシグナルを捕捉し、特定のコードを実行しました。
  3. クリーンアップ:後片付けの処理を自動化し、システムをクリーンな状態に保ちました。

プロフェッショナルなスクリプトでは、データの破損や不要なファイルの残存を防ぐため、終了時のさまざまな状況を適切に処理します。