Shellの基礎知識(実践編)

【Shellの基礎知識】共通関数定義クラスの完全ガイド!設計から実践まで徹底解説

運用自動化ツール

🟡 運用自動化ツール
📌 面倒な作業を一括効率化!シェルスクリプトで実現する運用自動化術
├─ 基本共通
| ├─【Shellの基礎知識】簡単なログ出力ロジックを作ってみました。
| ├─【Shellの基礎知識】シェルスクリプトの作成を時短!テンプレートで効率化する方法
| ├─【Shellの基礎知識】共通関数定義クラスの完全ガイド!設計から実践まで徹底解説
├─ 開発環境構築
| ├─【RHEL系Linux】開発サーバー初期設定スクリプトの完全自動化
| ├─【RHEL系Linux】Apache+Let's Encrypt 自動構築スクリプト|バーチャルホスト対応
| ├─【RHEL系Linux】Tomcatを自動インストール・設定するスクリプトの作成と活用法
| └─【RHEL系Linux】PostgreSQLを自動インストールするシェルスクリプトの使い方
└─ 運用・保守
  ├─【RHEL系Linux】任意サービスを簡単制御!汎用サービススクリプトの活用術
  ├─【RHEL系Linux】ファイルやログを自動圧縮する汎用スクリプトの実装と活用法
  ├─【RHEL系Linux】リソース(CPU・MEM)監視スクリプトで使用率・異常を検知する仕組み
  ├─【RHEL系Linux】中間ファイル連携を完全制御するファイル転送スクリプト
  ├─【RHEL系Linux】信頼性を重視した完了保証型ディレクトリ転送スクリプトの設計と実装
  ├─【RHEL系Linux】ディスク使用率を自動監視するシェルスクリプトの実装
  └─【RHEL系Linux】サーバーの障害検知と自動通知|systemdログ監視の実装例

共通関数定義クラスの概要と役割

共通関数定義クラス(utils.shrc)は、シェルスクリプト開発において頻繁に使用される汎用的な関数をまとめたスクリプトです。これを活用することで、スクリプトごとに重複したコードを書く必要がなくなり、開発の効率化や保守性の向上を実現できます。

特に、シェルスクリプトはプロジェクトが大規模になるにつれて管理が複雑になりがちです。utils.shrcを導入することで、スクリプト間の一貫性を保ちつつ、共通機能を統一した形式で提供できます。

共通関数定義クラスの目的

共通関数定義クラスの主な目的は、以下の3つに集約されます。

目的

  1. コードの再利用性向上
    頻繁に使用する関数を集約し、各スクリプトで共通化することで、コードの重複を削減します
  2. 保守性の向上
    修正が必要な場合、共通関数定義クラスを更新するだけで全スクリプトに適用されるため、保守作業を簡素化できます。
  3. 可読性の向上
    共通関数を利用することで、各スクリプトがシンプルになり、他の開発者がスクリプトを理解しやすくなります。

共通関数定義クラスの適用範囲

共通関数定義クラスは、以下のような用途で活用できます。

活用用途

  • ログ処理の統一
    メッセージの出力を標準化し、ログの記録方法を統一する。
  • エラーハンドリング
    予期しないエラー発生時に適切なメッセージを出力し、スクリプトの異常終了を制御する。
  • ファイル操作の簡素化
    一時ファイルの作成・削除、ディレクトリの作成・移動といった操作を統一する。
  • プロセス管理
    プロセスの状態チェックや重複実行の防止機能を提供する。
  • 環境変数の管理
    必要な環境変数の初期化や取得を一元管理し、スクリプトごとの設定ミスを防ぐ。

このように、システム管理やバッチ処理など、さまざまなシェルスクリプトに適用できます。

共通関数定義クラスの設計の基本方針

共通関数定義クラスは、以下の基本方針に基づいて設計されています。

基本方針

  1. シンプルで汎用的な関数の提供
    • 特定のスクリプト専用の関数ではなく、複数のスクリプトで再利用可能な設計を重視。
  2. 依存関係の最小化
    • 外部ツールや特定の環境に依存しないようにし、ポータビリティを確保。
  3. エラーハンドリングの標準化
    • すべての関数で適切なエラーハンドリングを実装し、異常時の動作を統一。
  4. パフォーマンスを考慮した設計
    • シェルスクリプトの特性を活かし、無駄なプロセスの生成や処理の遅延を最小限に抑える。
  5. 一貫した命名規則の適用
    • 変数や関数名の命名規則を統一し、可読性と保守性を向上。

これらの方針に基づいて設計することで、utils.shrcは柔軟かつ堅牢な共通関数クラスとして機能します。

設計書の構成と設計要素

共通関数定義クラス(utils.shrc)は、シェルスクリプトにおける汎用的な処理をまとめた関数群を提供するものです。本章では、その設計の前提条件、関数のスコープ管理、そして実装されている主要な関数について解説します。

クラス設計の前提条件

共通関数定義クラスは、シェルスクリプトの汎用的な処理を統一するために設計されています。そのため、以下のような前提条件を設定しています。

前提条件

  1. 関数ベースの設計
    • 共通関数定義クラス は、単体で実行するスクリプトではなく、他のスクリプトから source で読み込まれる ことを前提としています。
    • すべての処理は関数として定義されており、直接スクリプトに影響を与えることはありません。
  2. スコープの明確化(グローバル変数なし)
    • グローバル変数は一切使用しない 設計となっています。
    • 各関数の内部で local を利用し、関数外に影響を与えないように管理。
  3. エラーハンドリングと戻り値の統一
    • すべての関数は 0(成功)、1(失敗) など統一された戻り値を持つ。
    • 標準エラー出力(stderr)へエラーメッセージを出力する場合は、logOut 関数を利用。
  4. 外部依存の最小化
    • OSに依存しないよう、可能な限り POSIX準拠 のコマンドを使用。
    • grep, awk, sed, find など、シェルスクリプトで一般的なツールの利用は許容
  5. 排他制御とプロセス管理
    • acquireLock と releaseLock の関数を用意し、同時実行を制限できる設計。
    • ロックファイルの所有権を適切に管理する。

関数のスコープ管理と設計方針

utils.shrc では、すべての関数において ローカルスコープ(local 変数) を徹底し、グローバル変数の使用を避ける方針を採用しています。

スコープ管理の基本ルール

  • すべての関数で local を使用する

関数内でのみ有効な変数として local を明示的に使用し、他の関数やスクリプトに影響を与えないようにする。

sampleFunction() {
    local var="This is a local variable"
    echo "$var"
}

  • 関数間の影響を最小限にする

必要なデータは関数の引数で受け渡し、戻り値で結果を返す設計を採用。

isEmpty() {
    [[ -z "$1" ]]
}

  • ロックファイルの利用

排他制御を適切に管理するために、関数内でのみロックファイルを参照し、不要になったら適切に解放する。

releaseLock() {
    if [ -f "$LOCK_FILE" ]; then
        local 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 [$$]."
        fi
    fi
}

実装されている関数の詳細

共通関数定義クラス(utils.shrc)には、シェルスクリプトの開発を効率化するための汎用的な関数が実装されています。以下に、関数の一覧を示します。

関数名概要引数戻り値
releaseLockスクリプトの多重実行を防ぐためにロックを解除する。なし0: 成功(ロック解除)
1: 失敗(ロックが存在しない)
acquireLockスクリプトの多重実行を防ぐためにロックを取得する。なし0: 成功(ロック取得)
1: 失敗(他のプロセスがロック中)
setUTF8言語環境変数(LANG)を UTF-8 に設定する。なしなし
setSJIS言語環境変数(LANG)を Shift-JIS に設定する。なしなし
setLANGスクリプトを特定のユーザーで実行する。$1: 設定する LANG の値(デフォルト: C)なし
runAsroot ユーザーでスクリプトを実行する。$1: 実行ユーザー(デフォルト: root)
$2-: スクリプトに渡す引数
なし(指定ユーザーで再実行)
runAsRootスクリプトを root ユーザーとして実行する。なしなし(root ユーザーで実行)
abortエラーメッセージを記録し、スクリプトを終了する。$1: エラーメッセージなし(exit 1 で終了)
getMd5Sum指定したファイルの MD5 ハッシュ値を取得する。$1: ファイルパスMD5 ハッシュ値(標準出力へ出力)
getProcessCount指定したプロセスの数をカウントする。$1: プロセス名プロセス数(標準出力へ出力)
isAlphaNum文字列が英数字のみで構成されているかを判定する。$1: チェック対象の文字列0: 英数字 / 1: 非英数字
isNumeric文字列が数値であるかを判定する。$1: チェック対象の文字列0: 数値 / 1: 非数値
isPortAlive指定したポートがリスニング状態にあるかを確認する。$1: ポート番号0: LISTEN / 1: 非LISTEN
checkProcessCount指定したプロセスの実行数が指定範囲内であるかを確認する。$1: プロセス名
$2: 最小数
$3: 最大数
0: 範囲内 / 1: 少なすぎ / 2: 多すぎ
backupFileファイルをバックアップする(タイムスタンプ付き)。$1: バックアップ対象ファイルなし
deleteOldFiles指定したディレクトリ内の古いファイルを削除する。$1: ディレクトリパス
$2: 保持日数
なし
isHostAlive指定したホストがネットワーク上で到達可能かを確認する。$1: ホスト名0: 到達可能 / 1: 不可
confirmAction重要な操作の前にユーザーへ確認を求める。$1: 確認メッセージ0: 承諾 / 1: 拒否
checkCommandExistsスクリプトで必要なコマンドがシステムに存在するかを確認する。$1: コマンド名0: 存在 / 1: 不存在
isKeywordInProcess指定したキーワードがプロセス一覧に含まれているかを確認する。$1: キーワード0: 発見 / 1: 未発見
getCurrentTimestamp現在のタイムスタンプを取得する(YYYY-MM-DD_HH-MM-SS)。なしタイムスタンプ(標準出力へ出力)
formatDate指定した日時をフォーマット変換する。$1: 日時(YYYYMMDD-HHMMSS)フォーマット済み日時(標準出力へ出力)

共通関数定義クラスの導入手順

共通関数定義クラス( utils.shrc)を使用するための手順について説明します。これにより、シェルスクリプトにおける汎用的な関数を再利用し、効率的な開発が可能になります。

前提となる実行環境

Beエンジニアでシェルスクリプトを実行する環境は下記の通りとします。

実行環境

BASE_DIR(任意のディレクトリ)

  • scripts
    • bin(実行スクリプト格納領域)
      • <<各種実行スクリプト>>.sh (実行ファイル)
    • com(共通スクリプト格納領域)
      • logger.shrc(共通ログ出力ファイル)
      • utils.shrc(共通関数定義ファイル)
    • etc(設定ファイル等の格納領域)
      • infraMessage.conf(メッセージ定義ファイル)
    • log(スクリプト実行ログの格納領域)
      • スクリプト名.log 
    • tmp(テンポラリ領域)
    • rep(レポート出力領域)

共通関数定義クラス( utils.shrc) を正常に動作させるためには、以下の実行環境が整っている必要があります。

  • シェル環境: Bash 4.x 以上を推奨。 utils.shrc は Bash を前提として設計されています。
  • ファイルシステムへの書き込み権限: スクリプトがロックファイルや一時ファイルを操作するため、実行するディレクトリへの書き込み権限が必要です。
  • 依存ツール: 一部の関数は grepawksed などの標準的なツールを使用します。これらのツールがシステムにインストールされていることを確認してください。

本スクリプトの利用により生じた、いかなる損害についても一切の責任を負わないものとします。また、損害賠償等の義務ついても、一切責任を負いません。

共通関数定義クラスの導入方法

共通関数定義クラス( utils.shrc)は、以下のコードをプロジェクトにコピーし、シェルスクリプトで使用できるように設定します。
まず、下記のコードを utils.shrc としてファイルに保存してください。

# Common Utilities resource file
#_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#
# utils.sh ver.1.0.0 2025.01.24
#
# Usage:
#     Utils.shrc
#
# Description:
#     各種スクリプトより呼び出される共通機能を提供する。
#
# 設計書
#     none
#
#_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
# <変更履歴>
# Ver. 変更管理No. 日付        更新者       変更内容
# 1.0  PR-0001    2025/01/16 Bepro       新規作成
# 1.1  〇〇〇〇〇  2025/07/29  Bepro       CPU使用率取得メソッド追加
#_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

# --------------------------------------------------
# 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.
#   - It 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."
    fi
  fi
}

# --------------------------------------------------
# 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."
    return 1
  fi
}

# --------------------------------------------------
# Set LANG environment variable to UTF-8 encoding
# --------------------------------------------------
# Purpose:
#   Configures the LANG environment variable for UTF-8 encoding
#   based on the operating system. This ensures compatibility
#   with applications and scripts requiring UTF-8.
# Arguments:
#   None
# Returns:
#   None
# Notes:
#   - For AIX: LANG is set to "JA_JP"
#   - For Linux/FreeBSD: LANG is set to "ja_JP.UTF-8"
#   - For unsupported OS: LANG defaults to "C"
#   - Ensure the specified locale is installed on the system.
# --------------------------------------------------
setUTF8() {
  case $(uname -s) in
    AIX)           LANG=JA_JP ;;
    Linux|FreeBSD) LANG=ja_JP.UTF-8 ;;
    *)             LANG=C ;;
  esac
  export LANG
  logOut "INFO" "LANG set to ${LANG} based on operating system."
}

# --------------------------------------------------
# Set LANG environment variable to SJIS encoding
# --------------------------------------------------
# Purpose:
#   Configures the LANG environment variable for SJIS encoding
#   based on the operating system. This ensures compatibility
#   with software or scripts requiring SJIS encoding.
# Arguments:
#   None
# Returns:
#   None
# Notes:
#   - For AIX: LANG is set to "Ja_JP"
#   - For Linux: LANG is set to "ja_JP.sjis"
#   - For FreeBSD: LANG is set to "ja_JP.SJIS"
#   - For unsupported OS: LANG defaults to "C"
# --------------------------------------------------
setSJIS() {
  case $(uname -s) in
    AIX)     LANG=Ja_JP ;;
    Linux)   LANG=ja_JP.sjis ;;
    FreeBSD) LANG=ja_JP.SJIS ;;
    *)       LANG=C ;;
  esac
  export LANG
  logOut "INFO" "LANG set to ${LANG} based on operating system."
}

# --------------------------------------------------
# Set LANG
# --------------------------------------------------
# Purpose:
#   Sets the language environment variable (LANG).
# Arguments:
#   $1: The LANG value (default: C)
# Returns:
#   None
# Notes:
#   - Supports UTF-8, Shift-JIS, and C/POSIX locales.
#   - Logs the LANG setting for debugging.
# --------------------------------------------------
setLANG() {
  case "$1" in
    UTF-8|utf-8|UTF8|utf8|UTF) LANG="en_US.UTF-8" ;;
    SJIS|sjis|Shift-JIS) LANG="ja_JP.SJIS" ;;
    C|POSIX) LANG="C" ;;
    *) LANG="C" ;;
  esac
  export LANG
  logOut "INFO" "LANG has been set to [$LANG]"
}

# --------------------------------------------------
# Ensure the script runs as a specific user
# --------------------------------------------------
# Purpose:
#   Ensures the script is executed as a specific user.
#   If the current user is not the specified user, the script
#   switches to the target user and re-executes itself.
# Arguments:
#   param1: Target user (default: root)
#   param2-: Command line arguments to pass to the script
# Returns:
#   None (the script exits upon switching users or continues execution)
# Notes:
#   - Uses `sudo` to switch users instead of `su`.
#   - If the specified user does not exist, the script aborts.
# --------------------------------------------------
runAs() {
  local target_user=${1:-"root"}  # デフォルトは root
  shift
  local script_path

  # 現在のユーザーの取得
  local current_user
  current_user=$(id -un)

  # ユーザーの存在確認(getent passwd を使用)
  if ! getent passwd "$target_user" > /dev/null; then
    abort "User [$target_user] does not exist on this system."
  fi

  # 絶対パスを取得
  script_path=$(realpath "$0")

  # 実行ユーザーが異なれば sudo で再実行
  if [ "$current_user" != "$target_user" ]; then
    logOut "INFO" "Switching to user [$target_user] to execute [$script_path]"
    exec sudo -u "$target_user" -- "$script_path" "$@"
  fi

  logOut "INFO" "Executing as user [$current_user]"
}

# ------------------------------------------------------------------
# Function Name : getMd5sum
# Overview      : Retrieve the MD5 hash of a given file
# Description   :
#   Executes the md5sum command on the specified file and extracts
#   only the first column (the hash value) from the output.
#   Assumes the file existence check is handled by the caller.
#
# Arguments     : $1 = Path to the target file (absolute or relative)
# Return Value  : MD5 hash string (32-character alphanumeric)
# Used By       : Used for integrity verification in fileTransfer.sh etc.
# ------------------------------------------------------------------
getMd5sum() {
    md5sum "$1" | awk '{print $1}'
}

# --------------------------------------------------
# Ensure the script runs as root
# --------------------------------------------------
# Purpose:
#   Ensures the script is executed with root privileges.
#   If not, it either aborts with an error or re-executes with sudo.
# Arguments:
#   None
# Returns:
#   None (exits or re-executes the script as root)
# Notes:
#   - Automatically retries with 'sudo' if not root.
#   - This enhances usability while maintaining security.
# --------------------------------------------------
runAsRoot() {
  local uid
  uid=$(id -u)
  if [ "$uid" -ne 0 ]; then
    logOut "WARN" "Permission denied. Re-executing as root..."
    exec sudo "$0" "$@"
  fi
}

# --------------------------------------------------
# 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  # スクリプトを異常終了
}

# ------------------------------------------------------------
# Obtain the MD5 hash value of a file.
# ------------------------------------------------------------
# Purpose:
#   Computes and returns the MD5 hash value of the specified file.
# Arguments:
#   $1: Path to the file (absolute or relative)
# Returns:
#   MD5 hash value (printed to stdout)
# Notes:
#   - If the file does not exist or is not accessible, the function
#     logs an error and returns 1.
#   - Compatible with both Linux (`md5sum`) and macOS (`md5`).
# ------------------------------------------------------------
getMd5Sum() {
  local file="$1"

  # Check if the file path is provided
  if [ -z "$file" ]; then
    logOut "ERROR" "No file specified for MD5 calculation."
    return 1
  fi

  # Check if the file exists and is readable
  if [ ! -f "$file" ]; then
    logOut "ERROR" "File not found or not accessible: $file"
    return 1
  fi

  # Determine the correct MD5 command based on OS
  if command -v md5sum > /dev/null 2>&1; then
    md5sum "$file" | awk '{print $1}'
  elif command -v md5 > /dev/null 2>&1; then
    md5 -q "$file"
  else
    logOut "ERROR" "No suitable MD5 command found on this system."
    return 1
  fi
}

# ------------------------------------------------------------
# Check if a process with the given name is running.
# ------------------------------------------------------------
# Purpose:
#   Determines whether a process matching the given keyword is currently active.
# Arguments:
#   $1: The keyword to search for in the process list (e.g. "chronyd")
# Returns:
#   0: The process is running
#   1: The process is not running
# Notes:
#   - This function uses `ps` and `grep` to search for the process.
#   - It excludes lines that match `grep` itself and the current script name.
#   - Ensure $SCRIPT_NAME is defined in the calling context.
#   - May overmatch if $1 is a partial string (e.g. "chrony" matches "chronyc").
# ------------------------------------------------------------
isProcessAlive() {
  cnt=$(ps auxw | grep "$1" | grep -v grep | grep -v "$SCRIPT_NAME" | wc -l)
  if [ "$cnt" -eq 0 ]; then
    return 1
  fi
  return 0
}

# ------------------------------------------------------------
# Count the number of running processes matching a pattern.
# ------------------------------------------------------------
# Purpose:
#   Counts the number of running processes that match the specified pattern.
# Arguments:
#   $1: Pattern to search for (e.g., process name or part of a command line)
# Returns:
#   Process count (printed to stdout)
# Notes:
#   - Excludes the current script and the `grep` commands themselves from the count.
#   - Ensure that the pattern does not unintentionally match unrelated processes.
# ------------------------------------------------------------
getProcessCount() {
  local pattern="$1"

  # Check if the pattern is provided
  if [ -z "$pattern" ]; then
    echo "0"
    return
  fi

  # Count matching processes, excluding this script and grep itself
  local count
  count=$(ps auxw | grep -F "$pattern" | grep -v "grep" | grep -v "$SCRIPT_NAME" | wc -l)
  
  echo "$count"
}

# --------------------------------------------------
# Check if the value contains only alphanumeric characters.
# --------------------------------------------------
# Purpose:
#   Determines whether a given value consists solely of alphanumeric characters.
# Arguments:
#   $1: Value to check
# Returns:
#   0: Alphanumeric  1: Not Alphanumeric
# Notes:
#   - Uses `sed` to remove non-alphanumeric characters and compares with original value.
#   - Does not rely on external tools other than `sed` for matching.
# --------------------------------------------------
isAlphaNum() {
  val="$(echo $1 | sed -e 's/[^[:alnum:]]//g')"

  if [ "$val" != "$1" ]; then
    return 1  # Not Alphanumeric
  fi
  return 0  # Alphanumeric
}

# ------------------------------------------------------------
# Check if the given value is numeric.
# ------------------------------------------------------------
# Purpose:
#   Determines whether the provided value is a valid numeric value.
# Arguments:
#   $1: The value to check (string or number)
# Returns:
#   0: The value is numeric (integer)
#   1: The value is not numeric
# Notes:
#   - A numeric value is defined as an integer (positive or negative).
#   - This function does not handle floating-point numbers or other non-integer values.
#   - Leading or trailing spaces are not allowed.
#   - Uses `expr` to check if the value is a valid integer.
# ------------------------------------------------------------
isNumeric() {
  expr "$1" + 1 >/dev/null 2>&1
  if [ $? -ge 2 ]; then
    return 1
  fi
  return 0
}


# ------------------------------------------------------------
# Check if a specified port is in LISTEN state.
# ------------------------------------------------------------
# Purpose:
#   Verifies whether the specified port is actively listening
#   on the system.
# Arguments:
#   $1: Port number (e.g., 80, 443)
# Returns:
#   0: The port is listening
#   1: The port is not listening
# Notes:
#   - This function uses the `netstat` command to check the port state.
#   - Ensure that the `netstat` command is available in your environment.
#   - Invalid or empty input will cause the function to return 1.
# ------------------------------------------------------------
isPortAlive() {
  local port="$1"

  # Validate input
  if [[ -z "$port" || ! "$port" =~ ^[0-9]+$ ]]; then
    logOut "ERROR" "Invalid port number: $port"
    return 1
  fi

  # Check if the port is in LISTEN state
  local count
  count=$(netstat -tan 2>/dev/null | grep ":$port" | grep -w "LISTEN" | wc -l)

  if [ "$count" -eq 0 ]; then
    return 1  # Port is not listening
  fi
  return 0  # Port is listening
}

# ------------------------------------------------------------
# Check if the specified keyword is present in the process table.
# ------------------------------------------------------------
# Purpose:
#   Verifies whether the given keyword is present in the system's
#   process table.
# Arguments:
#   $1: Keyword to search for (e.g., process name or part of a command line)
# Returns:
#   0: The keyword is found in the process table
#   1: The keyword is not found in the process table
# Notes:
#   - Excludes the `grep` process itself from the search.
#   - If no keyword is provided, the function logs an error and returns 1.
#   - Ensure that the keyword does not unintentionally match unrelated processes.
# ------------------------------------------------------------
isKeywordInProcess() {
  local keyword="$1"

  # Validate input
  if [ -z "$keyword" ]; then
    logOut "ERROR" "No keyword specified for process search."
    return 1
  fi

  # Check for the keyword in the process table
  if ps auxw | grep -F "$keyword" | grep -v grep >/dev/null; then
    return 0  # Keyword found
  else
    return 1  # Keyword not found
  fi
}

# ------------------------------------------------------------
# Check the process count against a specified range.
# ------------------------------------------------------------
# Purpose:
#   Verifies whether the number of running processes for the
#   specified process name is within the given range.
# Arguments:
#   $1: process_name (string) - The name of the process to check.
#   $2: min_count (integer) - Minimum allowed process count.
#   $3: max_count (integer) - Maximum allowed process count.
# Returns:
#   0: Process count is within range.
#   1: Process count is below the minimum.
#   2: Process count exceeds the maximum.
#   3: Invalid argument (e.g., min_count > max_count or invalid process count).
# Notes:
#   - For single process requirements, set min_count and max_count to 1.
#   - Logs an error message if the process count is invalid or if the arguments are incorrect.
# ------------------------------------------------------------
checkProcessCount() {
    local process_name="$1"
    local min_count="$2"
    local max_count="$3"
    local process_count

    # プロセス数を数値として取得
    process_count=$(ps aux | grep "$process_name" | grep -v grep | wc -l)

    # プロセス数が空でないことを確認
    if [ -z "$process_count" ] || ! [[ "$process_count" =~ ^[0-9]+$ ]]; then
        echo "[ ERROR ] Invalid process count: $process_count"
        return 3  # 無効な値が返された場合
    fi

    # 引数のチェック: min_count が max_count より大きい場合、エラーを返す
    if [ "$min_count" -gt "$max_count" ]; then
        return 3  # 無効な引数
    fi

    # プロセス数が範囲内か確認
    if [ "$process_count" -ge "$min_count" ] && [ "$process_count" -le "$max_count" ]; then
        return 0  # プロセス数は範囲内
    elif [ "$process_count" -lt "$min_count" ]; then
        return 1  # プロセス数が最小値より少ない
    elif [ "$process_count" -gt "$max_count" ]; then
        return 2  # プロセス数が最大値を超えている
    fi
}

# ------------------------------------------------------------
# Get the current date in a specific format.
# ------------------------------------------------------------
# Purpose:
#   Returns the current date in the format YYYY-MM-DD.
# Arguments:
#   None
# Returns:
#   Current date (printed to stdout).
# ------------------------------------------------------------
getCurrentDate() {
    echo "$(date +"%Y-%m-%d" | tr -d '\r')"
}

# ------------------------------------------------------------
# Get the current timestamp in a specific format.
# ------------------------------------------------------------
# Purpose:
#   Returns the current date and time in the format YYYY-MM-DD HH:MM:SS.
# Arguments:
#   None
# Returns:
#   Current timestamp (printed to stdout).
# ------------------------------------------------------------
getCurrentTimestamp() {
  # シングルクォートを含まない形式で日付を取得
  echo "$(date +"%Y-%m-%d_%H-%M-%S")"
}

# ------------------------------------------------------------
# Format a given timestamp or current time.
# ------------------------------------------------------------
# Purpose:
#   Converts a given timestamp into a specified format.
# Arguments:
#   $1: Format (e.g., "%Y-%m-%d", "%H:%M:%S").
# Returns:
#   Formatted timestamp (printed to stdout).
# ------------------------------------------------------------
formatDate() {
  local format="${1:-"%Y-%m-%d"}"
  date +"$format"
}

# ------------------------------------------------------------
# Create a backup of a specified file.
# ------------------------------------------------------------
# Purpose:
#   Copies the file to a backup location with a timestamp appended.
# Arguments:
#   $1: File to back up.
# Returns:
#   None.
# Notes:
#   - Logs an error if the file does not exist or cannot be backed up.
# ------------------------------------------------------------
backupFile() {
  local file="$1"
  if [ ! -f "$file" ]; then
    logOut "ERROR" "File not found: $file"
    return 1
  fi

  local timestamp
  timestamp=$(getCurrentTimestamp)  # 共通関数でタイムスタンプ取得

  # バックアップファイル名を生成
  local backup="${file}_${timestamp}.bak"
  
  # バックアップを作成
  cp "$file" "$backup" && logOut "INFO" "Backup created: $backup" || logOut "ERROR" "Failed to create backup: $file"
}

# ------------------------------------------------------------
# Delete files older than a specified number of days.
# ------------------------------------------------------------
# Purpose:
#   Cleans up files in a directory that exceed a retention period.
# Arguments:
#   $1: Directory to clean up.
#   $2: Retention period in days.
# Returns:
#   None.
# Notes:
#   - Logs the result of the cleanup operation.
# ------------------------------------------------------------
deleteOldFiles() {
  local dir="$1"
  local days="$2"

  if [ ! -d "$dir" ]; then
    logOut "ERROR" "Directory not found: $dir"
    return 1
  fi

  find "$dir" -type f -mtime +"$days" -exec rm -f {} \; && logOut "INFO" "Old files in $dir older than $days days deleted." || logOut "ERROR" "Failed to delete files in $dir."
}

# ------------------------------------------------------------
# Check if a host is reachable.
# ------------------------------------------------------------
# Purpose:
#   Verifies whether the specified host is reachable via ping.
# Arguments:
#   $1: Hostname or IP address.
# Returns:
#   0: Host is reachable.
#   1: Host is not reachable.
# ------------------------------------------------------------
isHostAlive() {
  local host="$1"
  if ping -c 1 -W 1 "$host" >/dev/null 2>&1; then
    return 0
  else
    logOut "WARN" "Host $host is not reachable."
    return 1
  fi
}

# ------------------------------------------------------------
# Prompt the user for confirmation.
# ------------------------------------------------------------
# Purpose:
#   Asks the user for confirmation before proceeding with a critical operation.
# Arguments:
#   $1: Message to display (e.g., "Are you sure?")
# Returns:
#   0: User confirmed.
#   1: User declined.
# ------------------------------------------------------------
confirmAction() {
  local message="${1:-"Are you sure? [y/N]"}"
  read -r -p "$message " response
  case "$response" in
    [yY][eE][sS]|[yY]) return 0 ;;
    *) return 1 ;;
  esac
}

# ------------------------------------------------------------
# Check if a command exists in the environment.
# ------------------------------------------------------------
# Purpose:
#   Ensures that a required command is available before executing the script.
# Arguments:
#   $1: Command name to check (e.g., "curl").
# Returns:
#   0: Command exists.
#   1: Command does not exist.
# ------------------------------------------------------------
checkCommandExists() {
  command -v "$1" >/dev/null 2>&1  # コマンドが存在するか確認
  return $?  # 成功なら0、失敗なら1を返す
}

# ------------------------------------------------------------------
# Function name : getCpuUtilization
# Summary       : Get current CPU utilization rate
# Description   :
#   Executes the `top` command once to capture current CPU usage.
#   Calculates (100 - idle%) to derive the active usage percentage.
#
# Arguments     : None
# Return value  : CPU usage rate as an integer (%)
# Used in       : cpu_alert.sh
# ------------------------------------------------------------------
getCpuUtilization() {
    top -bn1 | grep "Cpu(s)" | \
        awk -F'id,' -v prefix="100 - " '{ split($1, vs, ","); v=vs[length(vs)]; sub("%", "", v); printf("%.0f", 100 - v) }'
}

# ------------------------------------------------------------------
# Function name : lower
# Summary       : Convert string to lowercase
# Arguments     : $1 - Input string
# Return value  : Lowercase string (via echo)
# ------------------------------------------------------------------
lower() {
    echo "$1" | tr '[:upper:]' '[:lower:]'
}

# ------------------------------------------------------------------
# Function name : getMemUtilization
# Summary       : Get current Memory utilization rate
# Description   :
#   Executes the `free` command to get memory statistics.
#   Calculates used memory as: (total - available) / total × 100
#
# Arguments     : None
# Return value  : Memory usage rate as an integer (%)
# Used in       : mem_alert.sh
# ------------------------------------------------------------------
getMemUtilization() {
    free | awk '/Mem:/ { printf("%.0f", ( ($2 - $7) / $2 ) * 100 ) }'
}

本スクリプトの利用により生じた、いかなる損害についても一切の責任を負わないものとします。また、損害賠償等の義務ついても、一切責任を負いません。

  1. utils.shrc を取得する
    utils.shrc ファイルをプロジェクトディレクトリにコピーします。通常は scripts/com/ ディレクトリに配置します。

    cp utils.shrc /path/to/your/project/scripts/com/

  2. 他のシェルスクリプトから source で読み込む
    他のシェルスクリプト内で共通関数を利用するためには、 source コマンドを使用して utils.shrc を読み込みます。

    source /path/to/your/project/scripts/com/utils.shrc

  3. 関数を利用する
    utils.shrc 内の関数は、スクリプト内でそのまま呼び出すことができます。例えば、 getCurrentTimestamp 関数を使用するには次のように記述します。

    timestamp=$(getCurrentTimestamp)
    echo "Current timestamp: $timestamp"

  4. 動作確認
    導入後、シェルスクリプトを実行して関数が正常に動作するかを確認します。エラーハンドリングやログ出力が適切に機能していることをチェックしてください。

この手順を参考にして、共通関数定義クラスをプロジェクトに導入し、スクリプト開発を効率化してください。

実践的な利用方法とサンプルコード

 utils.shrcは、シェルスクリプトでよく使われる便利な機能を集めた共通関数集です。これにより、日常的に行うタスクを簡素化し、効率的なスクリプト作成が可能になります。このセクションでは、utils.shrcの関数を実際にどのように活用するかを示すため、いくつかの実践的な利用方法とサンプルコードを紹介します。

utils.shrcには、ファイルバックアップやポートの状態確認、プロセス数の管理など、システム管理で役立つ多くの関数が含まれています。例えば、定期的なバックアップを自動化したり、ネットワークサービスが正常に稼働しているかどうかを簡単にチェックすることができます。これにより、手動で行う作業を減らし、エラーのリスクを低減することができます。

次に、utils.shrcを利用した具体的な使用例を紹介し、どのようにしてシェルスクリプト内でこれらの関数を活用できるかを見ていきましょう。

基本的な使い方

共通関数定義クラス( utils.shrc)を使用するには、まずそのファイルをプロジェクトに取り込み、 source コマンドで読み込みます。その後、クラス内で定義された関数を簡単に利用することができます。

よくある利用ケース

共通関数定義クラスは、システム管理やバッチ処理など、さまざまな場面で活用できます。以下は、よくある利用ケースの一例です。

  • プロセス管理
    getProcessCount を使用して、指定したプロセスが実行中かどうかを確認することができます。例えば、バックグラウンドで実行しているプロセスの数をカウントする場合に便利です。
  • ファイル操作
    backupFile 関数を使って、ファイルのバックアップを作成することができます。例えば、重要な設定ファイルをバックアップする際に役立ちます。

共通関数定義クラスを導入するメリット

共通関数定義クラスを導入することで、以下のようなメリットがあります。

  • コードの再利用性が向上
    同じ処理を複数のスクリプトで繰り返し記述する必要がなくなり、コードの再利用性が高まります。例えば、ファイル操作やプロセス管理などの汎用的な処理を共通化できます。
  • 保守性が向上
    処理を一元管理することができるため、スクリプトが増えてもメンテナンスが容易になります。もし変更が必要になった場合、 utils.shrc を更新するだけで、全スクリプトに反映されます。
  • コードの可読性が向上
    共通関数を使うことで、各スクリプトがシンプルになり、他の開発者がコードを理解しやすくなります。また、汎用関数の利用によって、スクリプトのロジックが明確に分かりやすくなります。
  • エラーハンドリングの標準化
    エラーハンドリングを一元管理できるため、異常が発生した場合に適切な対応を統一的に行えます。 utils.shrc では、エラー時にログを記録する機能を提供しており、異常を簡単にトラッキングできます。

トラブルシューティングとFAQ

共通関数定義クラス( utils.shrc)を利用する中で発生する可能性があるエラーとその対処方法、また、 utils.shrc の拡張方法について説明します。

よくあるエラーと対処方法

  • エラー: "Command not found"
    原因: utils.shrc 内で使用しているコマンドがシステムにインストールされていない。
    対処法: 必要なツール( psgrepwc など)がインストールされているかを確認し、インストールしてください。
    RHEL系の場合:

    sudo yum install procps

    または、`dnf` を使用する場合(RHEL 8 以降):

    sudo dnf install procps

  • エラー: "Permission denied"
    原因: スクリプトを実行する権限が不足している。
    対処法: スクリプトに実行権限を付与します。

    chmod +x /path/to/your/script.sh

  • エラー: "No such file or directory"
    原因: 指定されたファイルやディレクトリが存在しない。
    対処法: ファイルパスが正しいかを再確認し、存在することを確認してください。
  • エラー: ロックファイルが削除されていない
    原因: 他のプロセスがロックを保持したまま終了した場合。
    対処法: 手動でロックファイルを削除するか、スクリプトを修正して適切にロック解除を行うようにします。

共通関数定義クラスの拡張方法

utils.shrc は汎用的な関数を定義していますが、必要に応じて独自の関数を追加することも可能です。以下の手順で拡張できます。

  1. 新しい関数を追加する
    utils.shrc に新しい関数を追加することで、特定の用途に合わせた機能を追加できます。

    # 新しい関数の例
    myFunction() {
      echo "This is my custom function"
    }

  2. 関数をグローバルで使用できるようにする
    関数を追加した後、 source コマンドで utils.shrc を読み込むことで、すべてのシェルスクリプトでその関数を使用できるようにします。

    source /path/to/your/project/scripts/com/utils.shrc

  3. エラーハンドリングを追加する
    自作の関数にもエラーハンドリングを追加することで、予期しない問題が発生した際にも適切に対応できるようになります。

    myFunction() {
      if [ ! -f "$1" ]; then
        echo "Error: File not found!"
        exit 1
      fi # 関数の処理
    }

  4. 既存の関数をカスタマイズする
    utils.shrc にすでに存在する関数を変更して、自分のプロジェクトに合わせた動作をさせることもできます。ただし、他のスクリプトとの互換性を保つために慎重に変更を加えることが重要です。

よく読まれている記事

1

「私たちが日々利用しているスマートフォンやインターネット、そしてスーパーコンピュータやクラウドサービス――これらの多くがLinuxの力で動いていることをご存じですか? 無料で使えるだけでなく、高い柔軟 ...

2

Linux環境でよく目にする「Vim」という名前。サーバーにログインしたら突然Vimが開いてしまい、「どうやって入力するの?」「保存や終了ができない!」と困った経験をした人も多いのではないでしょうか。 ...

3

ネットワーク技術は現代のITインフラにおいて不可欠な要素となっています。しかし、ネットワークを深く理解するためには、その基本となる「プロトコル」と「レイヤ」の概念をしっかり把握することが重要です。 こ ...

4

この記事は、Linuxについて勉強している初心者の方向けに「Shellスクリプト」について解説します。最後まで読んで頂けましたら、Shellスクリプトはどのような役割を担っているのか?を理解出来るよう ...

5

Javaは世界中で広く使われているプログラミング言語であり、特に業務システムやWebアプリケーションの開発において欠かせない存在です。本記事では、初心者向けにJavaの基礎知識を網羅し、環境構築から基 ...

-Shellの基礎知識(実践編)