Shellの基礎知識(基礎編)

【Shellの基礎知識】関数の基本と応用|書式と戻り値を解説

Shell関数

一般的なプログラム言語でいう関数と同様、シェルスクリプトでも関数を定義する事が可能です。シェル関数は古くから組み込まれた、非常によく使われる機能です。ある名称で関数として定義し、一連のコマンドを処理させるだけのことです。

コマンド実行や変数定義などが可能なため、複数回同じ処理を行う場合は、関数に置き換え簡潔なコーディングとするのが良いでしょう。

シェルスクリプトで関数を使うメリットとは?

シェルスクリプトを活用する上で、関数を導入することには多くのメリットがあります。コードの効率性や保守性を高めるだけでなく、複雑なタスクの管理も容易にしてくれます。本セクションでは、関数を使う具体的なメリットを詳しく解説します。

コードの再利用性を高める

シェルスクリプトに関数を使用する最大のメリットの一つは、コードの再利用性を向上させることです。関数を作成することで、同じ処理を何度も記述する必要がなくなり、コードの重複を大幅に削減できます。

具体例:ファイル存在チェック関数

check_file_exists() {
  if [ -f "$1" ]; then
    echo "File exists: $1"
  else
    echo "File does not exist: $1"
  fi
}

# 関数を呼び出して再利用
check_file_exists /path/to/file1
check_file_exists /path/to/file2

 このように、一度作成した関数を使い回すことで、コードの効率化とエラーの発生リスクを軽減できます。

読みやすさと保守性の向上

シェルスクリプトで関数を使用することで、コードの構造が明確になり、全体の可読性が向上します。特に、長いスクリプトや複雑な処理を扱う場合、関数を使ってロジックを分割することで、問題箇所の特定や修正が容易になります。

読みやすいコードの例

# ログ出力関数
log_message() {
  echo "[$(date)] $1"
}
# メイン処理
main() {
  log_message "Processing started."
  # ここに処理を記述
  log_message "Processing completed."
}

main

 このように関数を使用することで、処理の流れがわかりやすくなり、後からコードを見返しても内容を理解しやすくなります。

複雑なタスクの分割と管理

関数を使うことで、スクリプト内の複雑なタスクを小さな単位に分割して管理することができます。これにより、各タスクのテストやデバッグが容易になり、全体の品質が向上します。

複雑なタスクを関数で分割

例えば、ディスクの使用状況を監視し、閾値を超えた場合に通知を送るスクリプトを作成する場合、以下のように関数でタスクを分割することが可能です。

# ディスク使用率チェッ
check_disk_usage() {
  usage=$(df -h | grep "$1" | awk '{print $5}' | sed 's/%//')
  echo $usage
}

#通知処理
send_alert() {
  echo "Disk usage alert: $1% on $2"
}

# メイン処理
main() {
  disk="/dev/sda1"
  usage=$(check_disk_usage $disk)
  if [ $usage -gt 80 ]; then
    send_alert $usage $disk
  fi
}

main

 このようにタスクを分割して関数化することで、コードが論理的かつ管理しやすくなります。

シェルスクリプト関数の基本構文

関数を定義する際は( )付きで関数名を記述し、中かっこ"{" で関数の始まりを示し、その次の行から処理内容をそれぞれ書いていきます。

処理が終わったら関数の終わりを示す中かっこ"}"の行を記述して終わりです。なお、関数名の前にfunctionをつける事もありますが、これは省略が可能です。

また、この関数を同じスクリプト内や関数を定義したファイルを読み込んだ別スクリプトで呼び出すときには( )は不要です。

関数の定義方法と呼び出し方

シェルスクリプトで関数を定義する際には、以下のような構文を使用します。

関数の書式

function 関数名 ( ) {
 関数内で実行するコマンド
}

関数名のあとの丸括弧は関数の定義として必要なものです。通常その中には何も書きません。

下記は、簡単な関数の記述例です。

hello_world() {
  echo "Hello World"
  uname -a
}

# 関数の呼び出し
hello_world 

このスクリプトを実行すると、"Hello, World"が出力されます。

次の様に、一行で記述することも可能です。

# hello_world() { echo "Hello World"; uname -a; }

シェルスクリプトにおいて、「;(セミコロン)」はコマンドの区切りを意味します。したがって、改行コードではなく「;(セミコロン)」を使うことによって、複数のコマンドを1行に記述することが出来ます。

コンソール上へ、文字列"hello"とOS情報が出力されました。

引数の受け渡しと利用法

関数には引数を渡すことができ、スクリプト内で柔軟な処理が可能になります。

関数の書式

function 関数名 ( ) {
 echo "1stArg   $1"
 echo "2ndArg $2
}

実例

add_numbers() {
  local sum=$(( $1 + $2 ))
  echo "The sum is: $sum"
}

実際にコンソール上で実行してみます。

ここでは「1stArg」と「2ndArg」の2つの引数を付与して実行します。

このスクリプトは、2つの数値を加算し、その結果を表示します。

戻り値の仕組みと注意点

シェルスクリプトでは、関数の戻り値として整数値を返すことができます。ただし、戻り値は通常、終了ステータスを示すために使用されます。

関数の書式

function_name() {
 # コマンドの実行
 retuen [n]
}

関数ではその関数の戻り値を自分で設定することができます。「return」コマンドを任意の位置に書き、そこで戻り値を設定できます。

return  [n]

[n]と書いている部分に任意の番号を設定すれば、それがこの関数の戻り値となります。

ここで返す値は通常のコマンドの実行終了ステータスと同じ取り扱いになり、シェル変数の「$?」で参照可能です。

明示的にreturnコマンドを書いていない場合には、「関数内の最後に実行されたコマンドの実行終了ステータス」になります。

実例

以下は、引数が偶数か奇数かを判定する関数の例です。

is_even() {
  if [ $(($1 % 2)) -eq 0 ]; then
    return 0 # 偶数の場合
  else
    return 1 # 奇数の場合
  fi
}

# 関数の呼び出し
is_even 4
if [ $? -eq 0 ]; then
  echo "The number is even."
else
  echo "The number is odd."
fi

実行例

    • returnで返せるのは0〜255の整数値に限られます。
    • 複雑なデータ(文字列や配列など)を返す場合は、echoを用いて標準出力を介する方法を使用します。

シェルのグローバル変数

シェルスクリプトでは、関数内で宣言(初使用)された変数は、デフォルトで呼び出し元スクリプト自体や、内部の他の関数でも利用可能なグローバル変数として振る舞います。

ここがほかのプログラミング言語などとは大きく異なる点です。

では、実際に試してみましょう。

実際に打ち込むコードは、下記になります。

# 関数作成
$ glb_func() {
    glb_test="hoge"
    echo ${glb_test}
}

#関数呼び出し
$ glb_func

#直接関数内の変数「glb_test」を参照
$ echo ${glb_test}

  1. 一度、コンソール上のプロンプトへ下記コマンドにて関数「glb_func」を作成します。
  2. その後、作成した関数「glb_func」を呼び出し、動作を確認します。ここまでは、普通の関数と同じですね。
  3. こんどは、作成した関数「glb_func」内部の変数「$glb_test」を、外部から直接呼び出してみます。

実行結果

外部からの変数「$glb_test」の呼び出しに、応答しているのが分かります。

変数「$glb_test」の値は、関数外でも値を保持されている為、関数終了後でもコマンドラインからの変数「$glb_test」呼び出しに応答します。

グローバル変数としたくない場合は、変数の前にlocalを指定すると、関数外では値が保持されないローカル変数として扱う事が可能です。

シェルのローカル変数

グローバル変数とは逆に、外部から参照できない変数として定義する場合には、ローカル変数を使用します。任意の変数の直前に「local」を付与します。

先ほどのシェル関数内の変数を、ローカル変数へ書き直してみましょう。ただし、混乱を避けるため関数名は「lcl_func」と変更しています。

# 関数作成(local)
$ lcl_func() {
    local lcl_test="hoge"
    echo ${lcl_test}
}

# 関数呼び出し
$ lcl_func

# 直接関数内の変数「lcl_test」を参照
$ echo ${lcl_test}

実行結果

ローカル変数が、外部から参照できないことが分かります。

シェルスクリプト関数の具体例

シェルスクリプトの関数は、様々な実用的なタスクを簡潔かつ効率的に実行するために活用できます。ここでは、日常的なシステム管理に役立つ具体例を紹介します。

ファイル存在確認の関数例

ファイルが存在するかを確認する関数を定義し、特定のファイルが存在しない場合にエラーメッセージを出力する方法を以下に示します。

check_file_exists() {
  if [ -f "$1" ]; then
    echo "File exists: $1"
  else
    echo "File does not exist: $1"
    return 1
  fi
}

# 関数の呼び出し
check_file_exists /path/to/target_file

 このスクリプトでは、引数として渡されたファイルパスを検証し、結果を標準出力に出力します。ファイルが存在しない場合には、終了ステータスが 1 になります。

ディスク使用量のチェック

システムのディスク使用量を監視し、特定の閾値を超えた場合にアラートを出す関数の例です。

check_disk_usage() {
  local disk=$1
  local threshold=$2

  usage=$(df -h | grep "$disk" | awk '{print $5}' | sed 's/%//')

  if [ "$usage" -ge "$threshold" ]; then
    echo "Disk usage alert: $usage% on $disk"
    return 1
  else
    echo "Disk usage is normal: $usage% on $disk"
    return 0
  fi
}

# 関数の呼び出し
check_disk_usage /dev/sda1 80

閾値超過時のアラート処理
ディスク使用率が閾値を超えた際の追加処理を以下に示します。

check_disk_usage_and_alert() {
  local disk=$1
  local threshold=$2
  if check_disk_usage "$disk" "$threshold"; then
    echo "No action required."
  else
    echo "Sending alert: Disk usage critical on $disk"
    # ここで通知メールやログ記録の処理を追加
  fi
}

# 関数の呼び出し
check_disk_usage_and_alert /dev/sda1 80

この例では、ディスク使用量が正常範囲内であれば「No action required」と出力され、閾値を超えている場合には「Sending alert」のメッセージが出力されます。

HTTPDの状態管理関数

Apache HTTP Server(HTTPD)の状態を管理するための関数を以下に示します。これには、HTTPDの状態確認、開始、および停止の機能が含まれます。

状態確認、開始、停止の処理

httpd_status() {
  systemctl status httpd
}
httpd_start() {
  systemctl start httpd
  echo "HTTPD started."
}
httpd_stop() {
  systemctl stop httpd
  echo "HTTPD stopped."
}

# 関数の呼び出し例
case $1 in
  status)
    httpd_status
    ;;
  start)
    httpd_start
    ;;
  stop)
    httpd_stop
    ;;
  *)
    echo "Usage: $0 {status|start|stop}"
    exit 1
    ;;
esac

 この例では、HTTPDの状態を確認する、サービスを開始する、サービスを停止する機能をそれぞれ別の関数として定義しています。スクリプトを引数付きで実行することで、目的の操作を実行できます。

シェルスクリプト関数の応用テクニック

シェルスクリプトの関数を効果的に活用することで、スクリプトの信頼性や柔軟性を大幅に向上させることができます。本セクションでは、関数の応用テクニックについて解説します。

エラー処理のベストプラクティス

エラー処理は、シェルスクリプトの信頼性を高める上で重要な要素です。適切なエラー処理を実装することで、予期しない問題を回避できます。

例1: 関数内でエラーメッセージを出力する

check_file_exists() {
  if [ ! -f "$1" ]; then
    echo "Error: File not found - $1" >&2
    return 1
  fi
  echo "File exists: $1"
}

# 関数の呼び出し
check_file_exists /path/to/file
if [ $? -ne 0 ]; then
  echo "File check failed." >&2
  exit 1
fi

例2: set -e を利用した自動エラー停止
スクリプト全体でエラー発生時に即座に停止する方法です。

set -e
process_file() {
  cp "$1" "$1.bak"
  echo "Backup created for $1"
}
process_file /path/to/file

ログ出力を活用したデバッグ

スクリプトの動作を追跡するために、ログ出力を効果的に利用します。これにより、エラー発生時の原因を特定しやすくなります。

例1: ログレベルの設定

log_message() {
 local level="$1"
 shift
 echo "[$(date +"%Y-%m-%d %H:%M:%S")] [$level] $@"
}

log_message "INFO" "Script started."
log_message "ERROR" "File not found."

例2: 関数ごとにデバッグ情報を記録

debug_mode=true
log_debug() {
  if [ "$debug_mode" = true ]; then
  echo "DEBUG: $@"
fi
}

process_data() {
  log_debug "Processing data file: $1"
  # データ処理コード
}
process_data /path/to/data

環境変数を利用した設定の柔軟化

環境変数を使用することで、スクリプトを再利用可能かつ柔軟に構成できます。設定値を変更する場合でも、スクリプトのコードを直接編集せずに済みます。

例1: 環境変数を使用した動的設定

例2: 環境変数によるスクリプト挙動の変更

LOG_DIR=${LOG_DIR:-/var/log/my_script}

log_message() {
  echo "[$(date)] $1" >> "$LOG_DIR/script.log"
}

log_message "Script execution started."

例2: 環境変数によるスクリプト挙動の変更

DEBUG_MODE=${DEBUG_MODE:-false}

run_task() {
  if [ "$DEBUG_MODE" = true ]; then
    echo "DEBUG: Running in debug mode."
  fi
  echo "Task executed."
}

run_task

シェルスクリプト関数の設計とベストプラクティス

シェルスクリプト関数を効率的かつ効果的に設計するためには、いくつかの基本的な原則やベストプラクティスを理解しておくことが重要です。このセクションでは、再利用性を意識した設計や、保守性を高めるためのポイントを解説します。

DRY原則を意識した関数設計

DRY(Don’t Repeat Yourself)原則を適用することで、同じ処理を複数箇所で記述するのを避け、コードの重複を最小限に抑えます。

ポイント

  • 冗長なコードの排除:
    繰り返し行われる処理を関数化することで、コードを簡潔に保つ。
  • メンテナンスの容易化:
    1箇所の関数を変更するだけで、スクリプト全体に影響を与えられる。
  • モジュール化の推進:
    汎用性の高い処理を関数として独立させることで、他のスクリプトでも活用できるようにする。

命名規則とコメントの重要性

関数の命名規則と適切なコメント付けは、スクリプトの可読性や保守性に直結します。

ポイント

  • 命名規則の一貫性:
    例えば、「func_」で始める、動詞を先頭にするなどのルールを決めるとよい。
    例: check_disk_space, backup_files
  • コメントの適切な活用:
    関数が何をするのかを1行で説明するコメントを追加する。
    例: # この関数はディスク使用量を確認し、警告を出力します

パラメータ化による柔軟性の向上

関数に引数を受け渡す設計を取り入れることで、柔軟性を向上させることができます。

引数の有効活用: 同じ関数を異なる目的で使用するために、パラメータを活用。

check_file_exists() {
  local file=$1
  if [ -e "$file" ]; then
    echo "ファイルが存在します: $file"
  else
    echo "ファイルが見つかりません: $file"
  fi
}

デフォルト値の設定: 引数が渡されなかった場合の処理を組み込むことで、関数の安全性を高める。

backup_dir=${1:-/default/backup/dir}

これらのベストプラクティスを取り入れることで、シェルスクリプト関数の設計はより効率的で保守性の高いものになります。初心者だけでなく、経験豊富なエンジニアにとっても有益なポイントです。

より実践的なシェルスクリプト関数例

シェルスクリプト関数を実際の業務で活用する方法を具体的に理解することで、より効率的なスクリプト設計が可能になります。このセクションでは、サーバー管理やデータ保護、外部サービスとの連携に役立つ実践的な関数例を紹介します。

ユーザーアカウントの監視と管理

サーバー上のユーザーアカウントを監視し、不正アクセスや不要なアカウントを検出するための関数例です。

例: アクティブなユーザーの確認と通知

check_active_users() {
  local active_users=$(who | awk '{print $1}' | sort | uniq)
  echo "現在ログイン中のユーザー:"
  echo "$active_users"

  # 管理者に通知(例: メール送信)
  if echo "$active_users" | grep -q "suspicious_user"; then
    echo "不正なユーザーを検出しました" | mail -s "警告: 不正なユーザー" admin@example.com
  fi
}

この関数を定期的に実行することで、不審なアクティビティを即座に検知し、対応が可能になります。

バックアップ自動化の関数化

データ保護を目的としたバックアップの自動化は、サーバー運用の基本です。以下の例では、指定したディレクトリをバックアップする関数を示します。

例: バックアップ作成関数

create_backup() {
  local source_dir=$1
  local backup_dir=${2:-/backup}
  local timestamp=$(date +"%Y%m%d%H%M%S")
  local backup_file="$backup_dir/backup_$timestamp.tar.gz"

  mkdir -p "$backup_dir"
  tar -czf "$backup_file" "$source_dir"

  echo "バックアップを作成しました: $backup_file"
}

  • この関数は、バックアップ元ディレクトリとバックアップ先ディレクトリを引数として受け取ります。
  • デフォルトのバックアップ先ディレクトリを指定しており、柔軟性が高い設計となっています。

APIのステータスチェック関数

外部サービスのAPIステータスを監視することで、障害やサービスダウンを即座に検知する仕組みを構築できます。

例: APIステータスの確認

check_api_status() {
  local api_url=$1
  local response=$(curl -s -o /dev/null -w "%{http_code}" "$api_url")

  if [ "$response" -eq 200 ]; then
    echo "APIは正常に動作しています: $api_url"
  else
    echo "APIに問題があります: $api_url (HTTPステータス: $response)"
    # 管理者に通知(例: ログ記録)
    echo "$(date): API異常検出 - $api_url" >> /var/log/api_check.log
  fi
}

  • この関数は、以下のような状況に対応できます:
    • APIが正常に応答しているかどうかをチェック
    • 異常時にログを記録してトラブルシューティングを容易にする

シェルスクリプト関数のデバッグと最適化

シェルスクリプトを効率的に運用するためには、エラーの早期発見とスクリプトの最適化が欠かせません。このセクションでは、シェルスクリプト特有のデバッグツールの活用方法と、スクリプトの実行速度を改善するテクニックについて解説します。

シェルスクリプト特有のデバッグツールの活用

シェルスクリプトには、エラーや不具合を発見するための便利なデバッグツールや機能が用意されています。以下に代表的なものを紹介します。

  1. set -x を使ったコマンドトレース
    set -x コマンドを使用すると、スクリプトの各コマンドが実行される前にその内容が出力されます。これにより、実行の流れを簡単に追跡できます。

#!/bin/bash
set -x # デバッグモードを有効化

my_function() {
  local file=$1
  if [ -e "$file" ]; then
    echo "ファイルが存在します: $file"
  else
    echo "ファイルが見つかりません: $file"
  fi
}

my_function "/path/to/file"
set +x # デバッグモードを無効化

  •  利点:
    実行中のコマンドを確認することで、誤った引数や不適切な条件を特定できます。
  • 注意点:
    実運用時には必ず無効化すること。大量のログが出力される可能性があります。
  1. trap を使ったエラーハンドリング
    trap コマンドを使用することで、エラーが発生した際に特定のアクションを実行するよう設定できます。

#!/bin/bash

trap 'echo "エラーが発生しました: ライン $LINENO"; exit 1' ERR

my_function() {
  local dir=$1
  cd "$dir" # 存在しないディレクトリの場合、エラーが発生
  echo "現在のディレクトリ: $(pwd)"
}

my_function "/invalid/path"

  •  利点:
    エラー発生時の状況を迅速に把握でき、原因究明に役立ちます。
  • 応用例:
    一時ファイルの削除やシステムリソースの解放処理を自動化できます。

実行速度の改善方法

スクリプトが遅い原因を特定し、効率化することで、パフォーマンスを大幅に向上させることができます。以下は、代表的な改善方法です。

  1. 無駄なコマンドの削減
    シェルスクリプトでは、外部コマンドの使用が一般的ですが、頻繁に呼び出すと実行速度に影響を与えます。

# 悪い例
for i in $(seq 1 100); do
 echo "$i" | grep "5"
done

#良い例(外部コマンドの使用を削減)
for i in {1..100}; do
if [[ "$i" == 5 ]]; then
  echo "$i"
fi
done

  1. 並列処理の活用
    xargs や & を使用して、複数のタスクを並列に実行することで処理時間を短縮できます。

# 並列処理を使用したバックアップ例
ls /source_dir | xargs -P 4 -I {} cp {} /backup_dir

 利点: CPUコアを最大限に活用することで、処理速度が向上します。

デバッグツールや最適化のテクニックを活用することで、シェルスクリプトの信頼性とパフォーマンスを大きく向上させることが可能です。これらの方法を実践しながら、効率的でエラーの少ないスクリプト作成を目指しましょう。

複数ファイルに分割して記述

冒頭でも触れましたが、関数を定義したファイル「comFunc.shrc(共通関数定義ファイル)」を外部ファイルとして作成しておき、「.(ドット)」コマンドまたは「source」コマンドで実行シェルスクリプトに読み込むことで、「comFunc.shrc」内に定義された関数を外部から使用することが可能です。

ちょっと難しそうですが、ファイルの分割はシェルスクリプトを作成していく過程で、非常に重要な概念です。

テンプレートファイルを使って、実際に試してみましょう。まず、下記の2つのシェルスクリプトを作成します。

  • comFunc.shrc(共通関数定義ファイル)
    本記事では、共通関数定義ファイルの拡張子に「resource」を表す「comFunc.shrc」を末尾に付与していますが、特に成約などはありません。
  • func.sh(実行シェルスクリプト)

共通関数定義ファイルの分割

[ <BASE_DIR>/scripts/com/comFunc.shrc ](共通関数定義ファイル)

実行シェルスクリプトの作成

[ <BASE_DIR>/scripts/bin/func.sh ] (実行シェルスクリプト)

実行結果を下記に示します。

実行シェルスクリプト「func.sh」内で、共通関数ファイル内の「glb_func」を呼び出し、変数「${glb_test}」の値ががターミナル上へ出力されました。

以上のことからもわかる通り、実行シェルスクリプトを作成する場合は、予め共通で使用するであろう関数群を共通関数定義ファイル(javaでいうところの共通クラス)として外部へ作成しておき、実行シェルスクリプトは、都度、必要に応じて共通関数定義ファイルを参照するように作成します。


この記事を読んだら、次は 「【Shellの基礎知識】シェル組み込みコマンドの高度な活用法|効率化と最適化のテクニック」 を読むのがおすすめです!

よく読まれている記事

1

IT入門シリーズ 🟢 STEP 1: ITの基礎を知る(ITとは何か?) 📌 IT初心者が最初に学ぶべき基本知識。ITの概念、ネットワーク、OS、クラウドの仕組みを学ぶ ...

2

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

3

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

-Shellの基礎知識(基礎編)
-, , , ,