特定用途のスクリプト実装後、クーロンにより毎日決まった時間に処理が実行されることを期待していたが「いつの間にかプロセスが落ちていた・・」
しかも「何時?プロセスが落ちたのか分からない」など、異常に気づいたころには「実は数ヶ月前から処理が止まっていた。」などはよくある話です。
大抵の場合、原因は多重起動処理の禁止ロジックを実装していないがために起こる悲劇です。
この問題を回避するべく「多重起動処理」を禁止にする仕組みを実装します。
Shellの基礎知識 🟢 Shell の基礎知識(基礎編)
📌 条件分岐やループなど、実務で通用する基本操作を網羅。
└─【Shellの基礎知識】構文体系を理解して実務に使える基礎力を身につける
├─【Shellの基礎知識】Shellスクリプト入門|初心者が押さえる基本
├─【Shellの基礎知識】変数と特殊変数の使い方|初心者向け解説
├─【Shellの基礎知識】Shell演算子の完全ガイド|基礎から応用まで
├─【Shellの基礎知識】条件分岐『if』『case』の使い方を解説
├─【Shellの基礎知識】ループ処理の基本|効率化と応用例を解説
├─【Shellの基礎知識】文字列置換の基本と応用|初心者向け解説
├─【Shellの基礎知識】複数行テキスト出力を簡単に!ヒアドキュメント活用法
├─【Shellの基礎知識】関数の基本と応用|書式と戻り値を解説
├─【Shellの基礎知識】組み込みコマンドの活用法|最適化テクニック
├─【Shellの基礎知識】クォートとコマンド置換の違いと使い分け
├─【Shellの基礎知識】リダイレクトの基本|標準入出力とエラー出力
├─【Shellの基礎知識】中級者向けShellスクリプト|実用的な書き方とコツ
└─【Shellの基礎知識】中級者向けShell活用術|10の応用Tipsを紹介
多重起動禁止処理の作成

定期的に実行するスクリプトが何らかの原因により、正常に実行できずに止まってしまう(キューイング)場合、(翌日の定時などに)後から起動された同一スクリプトのプロセスは次々にOSにキューイングされて行きます。
当然キューイングされたプロセス数分のリソースが消費され、やがてリソース不足に陥りシステムがダウンしてしまいます。
多重起動処理禁止に必要な機能
- ロック機能
- アンロック機能
- 処理の中断機能
上記3つの機能を実装して、多重起動処理検知時に当該プロセスを停止します。
前提となる実行環境
Beエンジニアでシェルスクリプトを実行する環境は下記の通りとします。
実行環境
BASE_DIR(任意のディレクトリ)
- scripts
- bin(実行スクリプト格納領域)
- <<各種実行スクリプト>>.sh (実行ファイル)
- com(共通スクリプト格納領域)
- logger.shrc(共通ログ出力ファイル)
- utils.shrc(共通関数定義ファイル)
- etc(設定ファイル等の格納領域)
- infraMessage.conf(メッセージ定義ファイル)
- log(スクリプト実行ログの格納領域)
- スクリプト名.log
- tmp(テンポラリ領域)
- rep(レポート出力領域)
- bin(実行スクリプト格納領域)
本処理の仕組みは、共通で使用することを前提に共通関数定義ファイル「utils.shrc」へ実装します。
ロック機能

スクリプトが起動した時点で「/tmp」ディレクトリ配下へ「スクリプト名.lock」ディレクトリを作成し、当該スクリプトが実行中であることを後続の同一スクリプトへ知らせるためのファイル「pid」ファイルを作成します。
[<BASE_DIR>/scripts/bin/utils.shrc ]
# --------------------------------------------------
# Acquire process lock using a lock file
# --------------------------------------------------
# Purpose:
# Prevent multiple instances of the script from running simultaneously.
# Arguments:
# None
# Returns:
# 0: Success (lock acquired)
# !0: Failure (another instance is already running)
# Notes:
# - Uses a lock file instead of a directory.
# - Relies on `flock` for safe locking.
# - Writes the process ID to the lock file.
# --------------------------------------------------
acquireLock() {
LOCK_FILE="${TMP_PATH}/${SCRIPT_NAME%.*}.lock"
exec 200>"$LOCK_FILE" # File descriptor 200 をロックファイルに関連付け
# flock コマンドを使用してロックを試みる
if flock -n 200; then
echo $$ > "$LOCK_FILE" # プロセスIDを書き込む
logOut "INFO" "Lock acquired on [$LOCK_FILE]."
export LOCK_FILE
return 0
else
logOut "ERROR" "Another instance is already running. Lock file [$LOCK_FILE] exists."
abort
fi
}
アンロック機能

当該スクリプトが終了した時点で「/tmp」ディレクトリ配下の「スクリプト名.lock」ディレクトリを削除します。
[<BASE_DIR>/scripts/bin/utils.shrc ]
# --------------------------------------------------
# Release the acquired lock
# --------------------------------------------------
# Purpose: # Removes the lock file to allow other instances to run.
# Arguments: # None # Returns: # None # Notes:
# - This function should be called before exiting the script. # - I
t verifies the lock file exists before removing.
# --------------------------------------------------
releaseLock() {
if [ -n "$LOCK_FILE" ]; then
locker=$(cat "$LOCK_FILE" 2>/dev/null)
if [ "$locker" = "$$" ]; then
rm -f "$LOCK_FILE"
logOut "INFO" "Lock released: [$LOCK_FILE]"
else
logOut "WARN" "Lock file exists, but owned by PID [$locker], not [$$]."
logOut "WARN" "Lock file [$LOCK_FILE] is left untouched."
abort
fi
fi
}
処理の中断機能

既に、同一スクリプトが実行中である場合、後発の同一スクリプト処理を中断します。
[<BASE_DIR>/scripts/bin/utils.shrc ]
# --------------------------------------------------
# Abort script execution with an error message.
# --------------------------------------------------
# Purpose:
# Logs an abort message and terminates the script with exit code 1.
# Arguments:
# param1-: Error message to log.
# Example: "Critical failure in configuration loading."
# Returns:
# None (terminates script with exit code 1)
# Notes:
# - All output is sent to standard error (stderr).
# - Includes stack trace information for easier debugging.
# --------------------------------------------------
abort() {
local caller_info log_msg
caller_info=$(caller 0 2>/dev/null) # どこで呼ばれたか取得
log_msg="ABORT [$caller_info]: $*" # `formatLog` の代わりに直接メッセージ生成
echo "$log_msg" 1>&2 # stderr にログ出力
sync # ログを確実にディスクに書き込む
exit 1 # スクリプトを異常終了
}
1呼び出し側のシェルスクリプト作成

実行ファイルへ多重起動禁止処理を実装し、実行後30秒間のスリープ処理を記述します。同一スクリプトを時間差で実行します。つまり後発スクリプトが先発スクリプト追い越すことを禁止します。
なお、ログの出力には下記の共通ログ出力クラスを使用しています。
[<BASE_DIR>/scripts/bin/func.sh ]
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
#!/bin/sh #_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ # # ver.1.0.0 yyyy.mm.dd # # Usage: # # sh /root/scripts/bin/utils.sh # # Description: # 例題コマンド実行スクリプト # # 設計書 # なし # #_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ . `dirname $0`/../com/utils.shrc . `dirname $0`/../com/logger.shrc # ---------------------------------------------------------- # variables (変数の宣言領域) # ---------------------------------------------------------- scope="var" rc=${JOB_ER} # ---------------------------------------------------------- # function (関数を記述する領域) # ---------------------------------------------------------- scope="func" # ---------------------------------------------------------- # Executes the processing at the end.. # ---------------------------------------------------------- # return N/A # ---------------------------------------------------------- terminate() { logOut "INFO" "中断検知:Trapによる終了処理を開始します。" releaseLock exitLog ${JOB_ER} exit 1 } # ---------------------------------------------------------- # pre-process (事前処理ロジックを記述する領域) # ---------------------------------------------------------- scope="pre" # Trap設定:中断(Ctrl+C)や終了シグナルを捕捉 # ※ 9(SIGKILL) は捕捉できず、誤動作を防ぐため指定しない trap terminate 1 2 3 15 # ログ開始関数呼び出し startLog if acquireLock; then logOut "INFO" "Successfully locked." else abort "ERROR" "Could not acquire lock." fi # ---------------------------------------------------------- # main-process (メインロジックを記述する領域) # ---------------------------------------------------------- scope="main" sleep 30 # ログ出力実行 logOut "INFO" "このメッセージは[ ${SCRIPT_NAME} ]の[ ${scope} ]領域から出力されています。" if [ $? -eq ${JOB_OK} ]; then rc=${JOB_OK} fi # ---------------------------------------------------------- # post-process (事後処理ロジックを記述する領域) # ---------------------------------------------------------- scope="post" # ログ終了関数呼び出し exitLog $rc |
多重起動禁止処理の実行結果
別々のコンソールから「utils.sh」をそれぞれ実行します。
|
1 2 3 4 5 6 7 8 9 |
[root@jb1 log]# tail -f utils.log 2021-03-13 17:07:00 [ pre ] SCRIPT:[ utils.sh ] PID:[ 17492 ] STARTED LOG. 2021-03-13 17:07:00 [ pre ] lock directory [/root/scripts/tmp/utils.lock] created. 2021-03-13 17:07:00 [ pre ] Successfully locked. 2021-03-13 17:07:05 [ pre ] SCRIPT:[ utils.sh ] PID:[ 17502 ] STARTED LOG. mkdir: cannot create directory ‘/root/scripts/tmp/utils.lock’: File exists 2021-03-13 17:07:05 [ pre ] ABORT PID:17502 Could not acquire lock. 2021-03-13 17:07:30 [ main ] このメッセージは[ utils.sh ]の[ main ]領域から出力されています。 2021-03-13 17:07:30 [ post ] SCRIPT:[ utils.sh ] PID:[ 17492 ] ENDED LOG. |
2行目:一発目のスクリプトがPID:17492で実行されました。
3行目:ロック機能により、ロックディレクトリが作成されました。
4行目:一発目のスクリプト(PID:17492)の成功ログが出力されています。
5行目:2発目の同一スクリプトがPID:17502で実行されました。
6行目:既にロックディレクトリが存在するため、新たなディレクトリの作成に失敗。
7行目:2発目の同一スクリプト処理が中断されました。
8行目:一発目のスクリプトの処理が実行されています。
9行目:一発目のスクリプトが正常に終了しました。
実践環境を整える
ここまで学んだ知識を実際に試すには、Linuxを動かす環境が必要です。手軽に始めるならVPSを利用するのがおすすめです。
→ VPS徹底比較!ConoHa・さくら・Xserverの選び方
VPSを利用してLinux環境を準備したら、実際の設定は下記の記事が参考になります。
→ VPSに開発環境を自動構築する方法|Apache+Tomcat+PostgreSQL




