シェルスクリプティング

自動化のためのスクリプト作成

基本構造

1#!/bin/bash
2# シバン(shebang): 実行するインタプリタを指定
3
4# コメント
5
6# 実行方法
7# chmod +x script.sh && ./script.sh
8# または
9# bash script.sh

変数

1#!/bin/bash
2
3# 変数代入(=の前後にスペース不可)
4name="Alice"
5count=10
6
7# 変数参照
8echo $name
9echo ${name}
10
11# 読み取り専用
12readonly PI=3.14
13
14# 配列
15arr=(one two three)
16echo ${arr[0]} # one
17echo ${arr[@]} # 全要素
18echo ${#arr[@]} # 要素数
19
20# コマンド引数
21$0 # スクリプト名
22$1, $2, ... # 引数
23$# # 引数の数
24$@ # 全引数(個別)
25$* # 全引数(一つの文字列)
26
27# 特殊変数
28$? # 直前の終了ステータス
29$$ # 現在のPID

条件分岐

1#!/bin/bash
2
3# if文
4if [ "$1" = "hello" ]; then
5 echo "Hello!"
6elif [ "$1" = "bye" ]; then
7 echo "Goodbye!"
8else
9 echo "Unknown"
10fi
11
12# 数値比較
13if [ $count -eq 10 ]; then echo "Equal"; fi
14# -eq 等しい, -ne 等しくない
15# -lt より小さい, -le 以下
16# -gt より大きい, -ge 以上
17
18# 文字列比較
19if [ "$str" = "test" ]; then echo "Match"; fi
20if [ -z "$str" ]; then echo "Empty"; fi # 空文字列
21if [ -n "$str" ]; then echo "Not empty"; fi
22
23# ファイルテスト
24if [ -f /path/to/file ]; then echo "File exists"; fi
25if [ -d /path/to/dir ]; then echo "Directory exists"; fi
26if [ -r file ]; then echo "Readable"; fi
27if [ -w file ]; then echo "Writable"; fi
28if [ -x file ]; then echo "Executable"; fi
29
30# 論理演算子
31if [ "$a" = "1" ] && [ "$b" = "2" ]; then echo "Both"; fi
32if [ "$a" = "1" ] || [ "$b" = "2" ]; then echo "Either"; fi
33
34# [[ ]] を使用(bashの拡張)
35if [[ "$str" == *.txt ]]; then echo "Text file"; fi
36if [[ "$str" =~ ^[0-9]+$ ]]; then echo "Number"; fi
37
38# case文
39case "$1" in
40 start)
41 echo "Starting..."
42 ;;
43 stop)
44 echo "Stopping..."
45 ;;
46 *)
47 echo "Usage: $0 {start|stop}"
48 exit 1
49 ;;
50esac

ループ

1#!/bin/bash
2
3# for文
4for i in 1 2 3 4 5; do
5 echo $i
6done
7
8# 範囲
9for i in {1..10}; do
10 echo $i
11done
12
13# 配列
14for item in "${arr[@]}"; do
15 echo $item
16done
17
18# ファイル
19for file in *.txt; do
20 echo "Processing $file"
21done
22
23# C言語スタイル
24for ((i=0; i<10; i++)); do
25 echo $i
26done
27
28# while文
29count=0
30while [ $count -lt 5 ]; do
31 echo $count
32 count=$((count + 1))
33done
34
35# ファイル読み込み
36while read line; do
37 echo "$line"
38done < file.txt
39
40# 無限ループ
41while true; do
42 # 処理
43 sleep 1
44done
45
46# break と continue
47for i in {1..10}; do
48 if [ $i -eq 5 ]; then continue; fi
49 if [ $i -eq 8 ]; then break; fi
50 echo $i
51done

関数

1#!/bin/bash
2
3# 関数定義
4greet() {
5 echo "Hello, $1!"
6}
7
8# 呼び出し
9greet "World"
10
11# 戻り値
12add() {
13 local result=$(($1 + $2))
14 echo $result
15}
16sum=$(add 3 5)
17echo $sum # 8
18
19# ローカル変数
20my_func() {
21 local var="local"
22 echo $var
23}
24
25# 終了ステータスを返す
26is_file() {
27 if [ -f "$1" ]; then
28 return 0 # 成功
29 else
30 return 1 # 失敗
31 fi
32}
33
34if is_file "/etc/passwd"; then
35 echo "File exists"
36fi

実践例

1#!/bin/bash
2# バックアップスクリプト
3
4set -e # エラーで停止
5set -u # 未定義変数でエラー
6set -o pipefail # パイプのエラーを検出
7
8# 設定
9BACKUP_DIR="/backup"
10SOURCE_DIR="/data"
11DATE=$(date +%Y%m%d)
12LOG_FILE="/var/log/backup.log"
13
14# ログ関数
15log() {
16 echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
17}
18
19# エラーハンドリング
20trap 'log "ERROR: Script failed on line $LINENO"; exit 1' ERR
21
22# メイン処理
23main() {
24 log "Backup started"
25
26 # ディレクトリ確認
27 if [ ! -d "$SOURCE_DIR" ]; then
28 log "ERROR: Source directory not found"
29 exit 1
30 fi
31
32 # バックアップ実行
33 tar -czf "${BACKUP_DIR}/backup_${DATE}.tar.gz" "$SOURCE_DIR"
34
35 # 古いバックアップ削除(7日以上)
36 find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
37
38 log "Backup completed"
39}
40
41main "$@"

SRE/インフラ観点

ベストプラクティス

  • set -e: エラーで即時停止
  • set -u: 未定義変数をエラーに
  • set -o pipefail: パイプのエラー検出
  • trapでエラーハンドリング
  • • ログ出力を必ず実装
  • • ShellCheck でスクリプトをチェック

よくある落とし穴

  • • 変数のクォート忘れ: "$var"
  • • スペースの扱い: =の前後にスペース不可
  • [[ vs [: bash拡張 vs POSIX
  • • 終了ステータスの確認忘れ