Linuxサーバー管理完全攻略 第6編:シェルスクリプト基礎
Linux Server Administration Complete Guide Part 6
Linuxサーバー管理完全攻略シリーズ
第5編:SSHとリモート管理 | 第6編:シェルスクリプト基礎 (現在) | 第7編:シェルスクリプト上級
はじめに:シェルスクリプトで自動化の世界へ
繰り返し作業を手動で処理することは時間の無駄であり、ミスの原因です。シェルスクリプトを活用すれば、複雑な作業を自動化し、一貫性のあるサーバー管理が可能です。今回はBashシェルスクリプトの基礎を解説します。
1. シェルスクリプトを始める
1.1 最初のスクリプト
#!/bin/bash
# 最初のシェルスクリプト
# ファイル名: hello.sh
echo "Hello, World!"
echo "現在時刻: $(date)"
echo "現在のユーザー: $USER"
echo "現在のディレクトリ: $PWD"
1.2 スクリプトの実行
# スクリプトファイル作成
vim hello.sh
# 実行権限を付与
chmod +x hello.sh
# 実行方法
./hello.sh # カレントディレクトリから実行
bash hello.sh # bashで直接実行
/path/to/hello.sh # 絶対パスで実行
# 実行権限なしで実行
bash hello.sh
sh hello.sh
1.3 Shebangの理解
#!/bin/bash # Bashシェルを使用
#!/bin/sh # POSIX互換シェルを使用
#!/usr/bin/env bash # 環境からbashを検索(移植性が高い)
#!/usr/bin/python3 # Pythonスクリプト
#!/usr/bin/perl # Perlスクリプト
2. 変数
2.1 変数の宣言と使用
#!/bin/bash
# 変数宣言(=の両側にスペースなし)
name="田中"
age=25
readonly PI=3.14159 # 読み取り専用変数
# 変数の使用
echo "名前: $name"
echo "年齢: ${age}歳" # 中括弧の使用を推奨
# 変数の削除
unset name
# デフォルト値の設定
echo "${undefined_var:-デフォルト}" # 値がなければデフォルトを出力
echo "${undefined_var:=新値}" # 値がなければ新値を代入
echo "${name:?変数がありません}" # 値がなければエラー出力
2.2 特殊変数
#!/bin/bash
# special_vars.sh arg1 arg2 arg3
echo "スクリプト名: $0"
echo "第1引数: $1"
echo "第2引数: $2"
echo "全引数: $@"
echo "全引数(文字列): $*"
echo "引数の数: $#"
echo "現在のプロセスID: $$"
echo "最後のバックグラウンドプロセスID: $!"
echo "最後のコマンド終了コード: $?"
2.3 環境変数
#!/bin/bash
# 主要な環境変数
echo "ホームディレクトリ: $HOME"
echo "パス: $PATH"
echo "現在のシェル: $SHELL"
echo "ユーザー名: $USER"
echo "ホスト名: $HOSTNAME"
echo "ターミナル種類: $TERM"
# 環境変数の設定
export MY_VAR="my value"
# すべての環境変数を確認
env
printenv
2.4 配列
#!/bin/bash
# 配列宣言
fruits=("りんご" "バナナ" "オレンジ" "ぶどう")
# 配列アクセス
echo "最初: ${fruits[0]}"
echo "3番目: ${fruits[2]}"
echo "全要素: ${fruits[@]}"
echo "配列長: ${#fruits[@]}"
# 配列修正
fruits[1]="マンゴー"
fruits+=("キウイ") # 要素追加
# 配列ループ
for fruit in "${fruits[@]}"; do
echo "果物: $fruit"
done
# 連想配列(Bash 4以降)
declare -A person
person[name]="田中"
person[age]=25
person[city]="東京"
echo "名前: ${person[name]}"
echo "全キー: ${!person[@]}"
3. 演算子
3.1 算術演算
#!/bin/bash
a=10
b=3
# 算術演算の方法
echo "加算: $((a + b))"
echo "減算: $((a - b))"
echo "乗算: $((a * b))"
echo "除算: $((a / b))"
echo "剰余: $((a % b))"
echo "累乗: $((a ** 2))"
# letコマンド
let "c = a + b"
echo "let結果: $c"
# exprコマンド(スペース必須)
result=$(expr $a + $b)
echo "expr結果: $result"
# インクリメント演算子
((a++))
echo "増加後: $a"
# 小数点計算(bc使用)
result=$(echo "scale=2; 10 / 3" | bc)
echo "小数点計算: $result"
3.2 文字列操作
#!/bin/bash
str="Hello, World!"
# 文字列長
echo "長さ: ${#str}"
# 部分文字列
echo "部分文字列: ${str:0:5}" # Hello
echo "後ろ5文字: ${str: -5}" # orld!
# 文字列置換
echo "置換: ${str/World/Japan}" # 最初のみ
echo "全置換: ${str//o/O}" # 全てのoをOに
# 文字列削除
filename="document.txt.bak"
echo "拡張子削除: ${filename%.*}" # document.txt
echo "全拡張子削除: ${filename%%.*}" # document
echo "パス削除: ${filename#*/}"
echo "プレフィックス削除: ${filename##*.}" # bak
# 大文字小文字変換(Bash 4以降)
text="Hello World"
echo "大文字: ${text^^}"
echo "小文字: ${text,,}"
4. 条件分岐
4.1 if文
#!/bin/bash
age=20
# 基本if文
if [ $age -ge 18 ]; then
echo "成人です"
fi
# if-else
if [ $age -ge 18 ]; then
echo "成人です"
else
echo "未成年です"
fi
# if-elif-else
if [ $age -lt 13 ]; then
echo "子供です"
elif [ $age -lt 20 ]; then
echo "ティーンエイジャーです"
else
echo "成人です"
fi
# [[ ]]使用(より多くの機能、Bash専用)
name="田中"
if [[ $name == "田中" && $age -ge 18 ]]; then
echo "成人の田中さんです"
fi
4.2 比較演算子
#!/bin/bash
# 数値比較
a=10
b=20
[ $a -eq $b ] # 等しい(equal)
[ $a -ne $b ] # 等しくない(not equal)
[ $a -lt $b ] # より小さい(less than)
[ $a -le $b ] # 以下(less or equal)
[ $a -gt $b ] # より大きい(greater than)
[ $a -ge $b ] # 以上(greater or equal)
# 文字列比較
str1="hello"
str2="world"
[ "$str1" = "$str2" ] # 等しい
[ "$str1" != "$str2" ] # 等しくない
[ -z "$str1" ] # 空文字列
[ -n "$str1" ] # 空でない
# [[ ]]での文字列比較
[[ $str1 == $str2 ]] # 等しい
[[ $str1 < $str2 ]] # 辞書順比較
[[ $str1 =~ ^h ]] # 正規表現マッチ
4.3 ファイルテスト
#!/bin/bash
file="/etc/passwd"
dir="/home"
# ファイル存在と種類
[ -e $file ] # 存在する
[ -f $file ] # 通常ファイル
[ -d $dir ] # ディレクトリ
[ -L $file ] # シンボリックリンク
[ -b $file ] # ブロックデバイス
[ -c $file ] # キャラクタデバイス
[ -S $file ] # ソケット
# ファイル権限
[ -r $file ] # 読み取り可能
[ -w $file ] # 書き込み可能
[ -x $file ] # 実行可能
# ファイル属性
[ -s $file ] # サイズが0でない
[ -O $file ] # 所有者が現在のユーザー
[ -G $file ] # グループが現在のグループ
# ファイル比較
[ $file1 -nt $file2 ] # より新しい(newer than)
[ $file1 -ot $file2 ] # より古い(older than)
[ $file1 -ef $file2 ] # 同じファイル(同じinode)
# 例
if [ -f "/etc/passwd" ] && [ -r "/etc/passwd" ]; then
echo "passwdファイルが存在し、読み取り可能です"
fi
4.4 case文
#!/bin/bash
echo "果物を選択してください(1-4):"
echo "1) りんご"
echo "2) バナナ"
echo "3) オレンジ"
echo "4) 終了"
read choice
case $choice in
1)
echo "りんごを選択しました"
;;
2)
echo "バナナを選択しました"
;;
3)
echo "オレンジを選択しました"
;;
4)
echo "終了します"
exit 0
;;
*)
echo "無効な選択です"
;;
esac
# パターンマッチング例
read -p "ファイル名入力: " filename
case $filename in
*.txt)
echo "テキストファイルです"
;;
*.jpg|*.png|*.gif)
echo "画像ファイルです"
;;
*.sh)
echo "シェルスクリプトです"
;;
*)
echo "不明なファイル形式です"
;;
esac
5. ループ
5.1 forループ
#!/bin/bash
# リスト反復
for fruit in りんご バナナ オレンジ ぶどう; do
echo "果物: $fruit"
done
# 範囲反復
for i in {1..5}; do
echo "数字: $i"
done
# ステップ指定
for i in {0..10..2}; do
echo "偶数: $i"
done
# Cスタイルforループ
for ((i=0; i<5; i++)); do
echo "インデックス: $i"
done
# ファイル反復
for file in /var/log/*.log; do
echo "ログファイル: $file"
done
# コマンド結果反復
for user in $(cat /etc/passwd | cut -d: -f1); do
echo "ユーザー: $user"
done
5.2 whileループ
#!/bin/bash
# 基本while
count=1
while [ $count -le 5 ]; do
echo "カウント: $count"
((count++))
done
# 無限ループ
while true; do
read -p "終了するには'q'を入力: " input
if [ "$input" = "q" ]; then
break
fi
done
# ファイル読み込み
while IFS= read -r line; do
echo "行: $line"
done < /etc/passwd
# パイプと併用
cat /etc/passwd | while read line; do
echo "$line"
done
# 複数フィールド読み込み
while IFS=: read user pass uid gid desc home shell; do
echo "ユーザー: $user, UID: $uid, ホーム: $home"
done < /etc/passwd
5.3 untilループ
#!/bin/bash
# until:条件が真になるまで繰り返し
count=1
until [ $count -gt 5 ]; do
echo "カウント: $count"
((count++))
done
# サービス待機例
until nc -z localhost 80 2>/dev/null; do
echo "Webサーバー起動待機中..."
sleep 1
done
echo "Webサーバーが起動しました!"
5.4 breakとcontinue
#!/bin/bash
# break:ループ終了
for i in {1..10}; do
if [ $i -eq 5 ]; then
echo "5で終了"
break
fi
echo "数字: $i"
done
# continue:次の反復へ
for i in {1..10}; do
if [ $((i % 2)) -eq 0 ]; then
continue # 偶数はスキップ
fi
echo "奇数: $i"
done
# ネストループでのbreak
for i in {1..3}; do
for j in {1..3}; do
if [ $j -eq 2 ]; then
break 2 # 外側のループまで終了
fi
echo "i=$i, j=$j"
done
done
6. 関数
6.1 関数の定義と呼び出し
#!/bin/bash
# 関数定義方法1
function greet {
echo "こんにちは!"
}
# 関数定義方法2(推奨)
greet2() {
echo "ようこそ!"
}
# 関数呼び出し
greet
greet2
6.2 関数引数
#!/bin/bash
# 引数を受け取る関数
print_info() {
echo "名前: $1"
echo "年齢: $2"
echo "全引数: $@"
echo "引数の数: $#"
}
print_info "田中" 25
# デフォルト値設定
greet() {
local name=${1:-"ゲスト"}
echo "こんにちは、${name}さん!"
}
greet "田中"
greet
6.3 戻り値
#!/bin/bash
# returnで終了コードを返す(0-255)
is_even() {
if [ $(($1 % 2)) -eq 0 ]; then
return 0 # 成功/真
else
return 1 # 失敗/偽
fi
}
if is_even 4; then
echo "4は偶数です"
fi
# 値を返す(echo使用)
get_double() {
echo $(($1 * 2))
}
result=$(get_double 5)
echo "5の2倍: $result"
# 複数の値を返す
get_stats() {
local sum=$(($1 + $2))
local diff=$(($1 - $2))
echo "$sum $diff"
}
read sum diff <<< $(get_stats 10 3)
echo "合計: $sum, 差: $diff"
6.4 ローカル変数
#!/bin/bash
global_var="グローバル"
test_scope() {
local local_var="ローカル"
global_var="関数で変更"
echo "関数内ローカル変数: $local_var"
echo "関数内グローバル変数: $global_var"
}
test_scope
echo "関数外グローバル変数: $global_var"
echo "関数外ローカル変数: $local_var" # 空
7. 入出力
7.1 ユーザー入力
#!/bin/bash
# 基本入力
echo "名前を入力してください:"
read name
echo "こんにちは、$nameさん!"
# プロンプト付き
read -p "年齢を入力してください: " age
# パスワード入力(非表示)
read -sp "パスワード: " password
echo ""
# タイムアウト設定
read -t 5 -p "5秒以内に入力してください: " input
# 文字数制限
read -n 1 -p "続行しますか? (y/n): " answer
echo ""
# 配列として読み込み
read -a colors -p "好きな色(スペース区切り): "
echo "最初の色: ${colors[0]}"
7.2 出力
#!/bin/bash
# echo
echo "通常出力"
echo -n "改行なし"
echo -e "タブ\t改行\n特殊文字"
# printf(フォーマット指定)
printf "名前: %s, 年齢: %d\n" "田中" 25
printf "小数点: %.2f\n" 3.14159
printf "%-10s | %5d\n" "りんご" 100
printf "%-10s | %5d\n" "バナナ" 50
# 色付き出力
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
echo -e "${RED}エラーメッセージ${NC}"
echo -e "${GREEN}成功メッセージ${NC}"
7.3 リダイレクション
#!/bin/bash
# 標準出力リダイレクション
echo "ログ内容" > logfile.txt # 上書き
echo "追加内容" >> logfile.txt # 追記
# 標準エラーリダイレクション
command 2> error.log # エラーのみ
command > output.log 2>&1 # 出力とエラー両方
command &> all.log # 出力とエラー両方(Bash)
# 標準入力リダイレクション
while read line; do
echo "$line"
done < input.txt
# Here Document
cat << EOF
複数行の
テキストを
一度に出力します。
変数置換: $USER
EOF
# Here String
grep "pattern" <<< "検索する文字列"
# パイプ
cat /etc/passwd | grep "root" | cut -d: -f1
8. コマンド実行と置換
8.1 コマンド置換
#!/bin/bash
# $()使用(推奨)
current_date=$(date)
file_count=$(ls -1 | wc -l)
# バッククォート使用(旧式)
current_date=`date`
# ネスト可能
inner=$(echo "Hello $(whoami)")
echo "現在の日付: $current_date"
echo "ファイル数: $file_count"
8.2 コマンド実行結果の確認
#!/bin/bash
# 終了コード確認
ls /nonexistent 2>/dev/null
if [ $? -eq 0 ]; then
echo "成功"
else
echo "失敗"
fi
# 条件文で直接使用
if grep -q "root" /etc/passwd; then
echo "rootユーザーが存在します"
fi
# && と ||
mkdir test_dir && echo "ディレクトリ作成成功"
rm nonexistent 2>/dev/null || echo "ファイルがありません"
# コマンドグループ
{ echo "1行目"; echo "2行目"; } > output.txt
9. 実用的なスクリプト例
9.1 システム情報スクリプト
#!/bin/bash
# system_info.sh - システム情報出力
echo "========================================"
echo " システム情報レポート"
echo "========================================"
echo ""
echo "ホスト名: $(hostname)"
echo "OS: $(uname -o)"
echo "カーネル: $(uname -r)"
echo "アーキテクチャ: $(uname -m)"
echo ""
echo "--- CPU情報 ---"
echo "CPU: $(grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2)"
echo "コア数: $(nproc)"
echo ""
echo "--- メモリ情報 ---"
free -h | grep -E "Mem|Swap"
echo ""
echo "--- ディスク使用量 ---"
df -h | grep -E "^/dev"
echo ""
echo "--- ネットワーク情報 ---"
ip -4 addr show | grep inet | awk '{print $2}'
echo ""
echo "--- システム稼働時間 ---"
uptime
echo ""
echo "========================================"
9.2 バックアップスクリプト
#!/bin/bash
# backup.sh - シンプルなバックアップスクリプト
# 設定
SOURCE_DIR="/home/$USER/documents"
BACKUP_DIR="/home/$USER/backups"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"
# バックアップディレクトリ作成
mkdir -p "$BACKUP_DIR"
# バックアップ実行
echo "バックアップ開始: $SOURCE_DIR"
if tar -czf "${BACKUP_DIR}/${BACKUP_FILE}" -C "$(dirname $SOURCE_DIR)" "$(basename $SOURCE_DIR)" 2>/dev/null; then
echo "バックアップ完了: ${BACKUP_DIR}/${BACKUP_FILE}"
echo "ファイルサイズ: $(du -h ${BACKUP_DIR}/${BACKUP_FILE} | cut -f1)"
else
echo "バックアップ失敗!"
exit 1
fi
# 古いバックアップ削除(7日以上)
echo ""
echo "古いバックアップを整理中..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
echo "完了!"
9.3 ログ監視スクリプト
#!/bin/bash
# log_monitor.sh - ログファイル監視
LOG_FILE="${1:-/var/log/syslog}"
KEYWORD="${2:-error}"
if [ ! -f "$LOG_FILE" ]; then
echo "ファイルが存在しません: $LOG_FILE"
exit 1
fi
echo "ログ監視開始: $LOG_FILE"
echo "キーワード: $KEYWORD"
echo "終了: Ctrl+C"
echo "----------------------------"
tail -f "$LOG_FILE" | while read line; do
if echo "$line" | grep -qi "$KEYWORD"; then
echo "[$(date '+%H:%M:%S')] $line"
fi
done
10. デバッグ
10.1 デバッグモード
#!/bin/bash
# スクリプト全体をデバッグ
bash -x script.sh
# スクリプト内でデバッグ有効化
set -x # デバッグ開始
# コマンド群...
set +x # デバッグ終了
# 特定部分のみデバッグ
debug() {
if [ "$DEBUG" = "1" ]; then
echo "[DEBUG] $*" >&2
fi
}
DEBUG=1
debug "これはデバッグメッセージです"
10.2 エラー処理
#!/bin/bash
# エラー発生時に即座に終了
set -e
# 未定義変数使用時にエラー
set -u
# パイプエラー検出
set -o pipefail
# 全て設定
set -euo pipefail
# エラーハンドラー
trap 'echo "エラー発生: 行 $LINENO"; exit 1' ERR
# 終了時の整理
cleanup() {
echo "クリーンアップ実行..."
rm -f /tmp/tempfile
}
trap cleanup EXIT
まとめ
シェルスクリプトの基礎を学びました。変数、条件分岐、ループ、関数を組み合わせて、様々な自動化スクリプトを作成できます。次回は、正規表現、テキスト処理、高度なパターンなどシェルスクリプト上級テクニックを解説します。