シェルスクリプティング
自動化のためのスクリプト作成
基本構造
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 # 変数代入(=の前後にスペース不可) 4 name="Alice" 5 count=10 6 7 # 変数参照 8 echo $name 9 echo ${name} 10 11 # 読み取り専用 12 readonly PI=3.14 13 14 # 配列 15 arr=(one two three) 16 echo ${arr[0]} # one 17 echo ${arr[@]} # 全要素 18 echo ${#arr[@]} # 要素数 19 20 # コマンド引数 21 $0 # スクリプト名 22 $1, $2, ... # 引数 23 $# # 引数の数 24 $@ # 全引数(個別) 25 $* # 全引数(一つの文字列) 26 27 # 特殊変数 28 $? # 直前の終了ステータス 29 $$ # 現在のPID
条件分岐
1 #!/bin/bash 2 3 # if文 4 if [ "$1" = "hello" ]; then 5 echo "Hello!" 6 elif [ "$1" = "bye" ]; then 7 echo "Goodbye!" 8 else 9 echo "Unknown" 10 fi 11 12 # 数値比較 13 if [ $count -eq 10 ]; then echo "Equal"; fi 14 # -eq 等しい, -ne 等しくない 15 # -lt より小さい, -le 以下 16 # -gt より大きい, -ge 以上 17 18 # 文字列比較 19 if [ "$str" = "test" ]; then echo "Match"; fi 20 if [ -z "$str" ]; then echo "Empty"; fi # 空文字列 21 if [ -n "$str" ]; then echo "Not empty"; fi 22 23 # ファイルテスト 24 if [ -f /path/to/file ]; then echo "File exists"; fi 25 if [ -d /path/to/dir ]; then echo "Directory exists"; fi 26 if [ -r file ]; then echo "Readable"; fi 27 if [ -w file ]; then echo "Writable"; fi 28 if [ -x file ]; then echo "Executable"; fi 29 30 # 論理演算子 31 if [ "$a" = "1" ] && [ "$b" = "2" ]; then echo "Both"; fi 32 if [ "$a" = "1" ] || [ "$b" = "2" ]; then echo "Either"; fi 33 34 # [[ ]] を使用(bashの拡張) 35 if [[ "$str" == *.txt ]]; then echo "Text file"; fi 36 if [[ "$str" =~ ^[0-9]+$ ]]; then echo "Number"; fi 37 38 # case文 39 case "$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 ;; 50 esac
ループ
1 #!/bin/bash 2 3 # for文 4 for i in 1 2 3 4 5; do 5 echo $i 6 done 7 8 # 範囲 9 for i in {1..10}; do 10 echo $i 11 done 12 13 # 配列 14 for item in "${arr[@]}"; do 15 echo $item 16 done 17 18 # ファイル 19 for file in *.txt; do 20 echo "Processing $file" 21 done 22 23 # C言語スタイル 24 for ((i=0; i<10; i++)); do 25 echo $i 26 done 27 28 # while文 29 count=0 30 while [ $count -lt 5 ]; do 31 echo $count 32 count=$((count + 1)) 33 done 34 35 # ファイル読み込み 36 while read line; do 37 echo "$line" 38 done < file.txt 39 40 # 無限ループ 41 while true; do 42 # 処理 43 sleep 1 44 done 45 46 # break と continue 47 for i in {1..10}; do 48 if [ $i -eq 5 ]; then continue; fi 49 if [ $i -eq 8 ]; then break; fi 50 echo $i 51 done
関数
1 #!/bin/bash 2 3 # 関数定義 4 greet() { 5 echo "Hello, $1!" 6 } 7 8 # 呼び出し 9 greet "World" 10 11 # 戻り値 12 add() { 13 local result=$(($1 + $2)) 14 echo $result 15 } 16 sum=$(add 3 5) 17 echo $sum # 8 18 19 # ローカル変数 20 my_func() { 21 local var="local" 22 echo $var 23 } 24 25 # 終了ステータスを返す 26 is_file() { 27 if [ -f "$1" ]; then 28 return 0 # 成功 29 else 30 return 1 # 失敗 31 fi 32 } 33 34 if is_file "/etc/passwd"; then 35 echo "File exists" 36 fi
実践例
1 #!/bin/bash 2 # バックアップスクリプト 3 4 set -e # エラーで停止 5 set -u # 未定義変数でエラー 6 set -o pipefail # パイプのエラーを検出 7 8 # 設定 9 BACKUP_DIR="/backup" 10 SOURCE_DIR="/data" 11 DATE=$(date +%Y%m%d) 12 LOG_FILE="/var/log/backup.log" 13 14 # ログ関数 15 log() { 16 echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" 17 } 18 19 # エラーハンドリング 20 trap 'log "ERROR: Script failed on line $LINENO"; exit 1' ERR 21 22 # メイン処理 23 main() { 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 41 main "$@"
SRE/インフラ観点
ベストプラクティス
- •
set -e: エラーで即時停止 - •
set -u: 未定義変数をエラーに - •
set -o pipefail: パイプのエラー検出 - •
trapでエラーハンドリング - • ログ出力を必ず実装
- • ShellCheck でスクリプトをチェック
よくある落とし穴
- • 変数のクォート忘れ:
"$var" - • スペースの扱い:
=の前後にスペース不可 - •
[[vs[: bash拡張 vs POSIX - • 終了ステータスの確認忘れ