1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

シェルスクリプト文法まとめ|変数・条件分岐・ループ・関数を一気に整理

1
Posted at

はじめに

シェルスクリプト(bash)は、Linux/macOS の自動化・バッチ処理・CI/CDに欠かせない技術です。しかし文法が独特で「なんとなく動いている」状態になりがちです。本記事では文法を体系的に整理します。


0. 基本のキ

シバン(shebang)

スクリプトの1行目に書く、使用するシェルの指定です。

#!/bin/bash
#!/usr/bin/env bash   # より移植性が高い書き方

実行権限と実行

chmod +x script.sh   # 実行権限を付与
./script.sh          # 実行
bash script.sh       # bash で直接実行(権限不要)

コメント

# これはコメント
echo "hello"  # 行末コメントも可

1. 変数

変数の定義と参照

name="田中"          # = の前後にスペースを入れない(重要!)
echo $name           # 田中
echo "${name}さん"   # 田中さん(波括弧で区切りを明示)

よくあるミス: name = "田中" はコマンドとして解釈されエラーになります。

変数の種類

# ローカル変数(そのシェルのみ有効)
count=10

# 環境変数(子プロセスにも引き継がれる)
export PATH="$PATH:/usr/local/bin"

# 読み取り専用
readonly MAX=100

# 変数の削除
unset count

クォートの使い分け

記法 変数展開 用途
"二重引用符" される スペースを含む値・変数展開あり
'一重引用符' されない リテラル文字列
`バッククォート` コマンド実行 非推奨($() を使う)
name="世界"
echo "Hello $name"   # Hello 世界(変数展開される)
echo 'Hello $name'   # Hello $name(展開されない)

コマンド置換

コマンドの出力を変数に代入します。

today=$(date +%Y-%m-%d)
echo "今日は $today です"

files=$(ls *.txt)

2. 特殊変数

変数 意味
$0 スクリプト名
$1 $2 ... 第1引数、第2引数...
$# 引数の個数
$@ 全引数(各引数を個別に扱う)
$* 全引数(1つの文字列として扱う)
$? 直前のコマンドの終了ステータス(0=成功)
$$ 現在のシェルのPID
$! 直前にバックグラウンド実行したコマンドのPID
#!/bin/bash
echo "スクリプト名: $0"
echo "引数の数: $#"
echo "第1引数: $1"
echo "第2引数: $2"
echo "全引数: $@"
$ ./script.sh foo bar
スクリプト名: ./script.sh
引数の数: 2
第1引数: foo
第2引数: bar
全引数: foo bar

3. 算術演算

a=10
b=3

# $(( )) で算術演算
echo $((a + b))   # 13
echo $((a - b))   # 7
echo $((a * b))   # 30
echo $((a / b))   # 3(整数除算)
echo $((a % b))   # 1(余り)
echo $((a ** b))  # 1000(べき乗)

# インクリメント
count=0
((count++))
echo $count  # 1

# let コマンド
let result=a*b
echo $result  # 30

浮動小数点が必要な場合は bc コマンドを使います。

echo "scale=2; 10/3" | bc   # 3.33

4. 文字列操作

str="Hello, World"

# 文字列長
echo ${#str}              # 12

# 部分文字列(${変数:開始:長さ})
echo ${str:0:5}           # Hello
echo ${str:7}             # World

# 前方一致削除(最短)
echo ${str#Hello, }       # World

# 前方一致削除(最長)
echo ${str##*,}           #  World

# 後方一致削除(最短)
echo ${str%,*}            # Hello

# 置換(最初の1つ)
echo ${str/World/Shell}   # Hello, Shell

# 置換(すべて)
echo ${str//l/L}          # HeLLo, WorLd

# デフォルト値(変数が未定義または空のとき)
echo ${name:-"名無し"}    # name が空なら「名無し」

# 大文字・小文字変換(bash 4.0+)
echo ${str^^}   # HELLO, WORLD
echo ${str,,}   # hello, world

5. 条件分岐

if 文

if [ 条件 ]; then
    処理
elif [ 条件 ]; then
    処理
else
    処理
fi

条件式の種類

文字列比較

if [ "$a" = "$b" ]; then   # 等しい(= を使う)
if [ "$a" != "$b" ]; then  # 等しくない
if [ -z "$a" ]; then       # 空文字
if [ -n "$a" ]; then       # 空文字でない

数値比較

演算子 意味
-eq 等しい(equal)
-ne 等しくない(not equal)
-lt より小さい(less than)
-le 以下(less or equal)
-gt より大きい(greater than)
-ge 以上(greater or equal)
a=10
if [ $a -gt 5 ]; then
    echo "5より大きい"
fi

ファイル・ディレクトリ判定

演算子 意味
-e file 存在する
-f file ファイルとして存在する
-d file ディレクトリとして存在する
-r file 読み取り可能
-w file 書き込み可能
-x file 実行可能
-s file サイズが0より大きい
if [ -f "config.txt" ]; then
    echo "設定ファイルが存在します"
fi

論理演算子

# AND
if [ $a -gt 0 ] && [ $a -lt 100 ]; then

# OR
if [ $a -eq 0 ] || [ $a -eq 1 ]; then

# NOT
if ! [ -f "file.txt" ]; then

[[ ]] vs [ ]

[[ ]] は bash 拡張で、より安全で高機能です。

# [[ ]] はパターンマッチ・正規表現が使える
if [[ "$name" ==* ]]; then    # 前方一致
if [[ "$str" =~ ^[0-9]+$ ]]; then  # 正規表現

# [[ ]] は変数をクォートしなくてもスペースで壊れない
if [[ $name == "田中 太郎" ]]; then  # OK
if [ $name == "田中 太郎" ]; then    # NG(スペースで引数が分割される)

case 文

case "$1" in
    start)
        echo "起動します"
        ;;
    stop)
        echo "停止します"
        ;;
    restart)
        echo "再起動します"
        ;;
    *)
        echo "使い方: $0 {start|stop|restart}"
        exit 1
        ;;
esac

6. ループ

for 文

# リストのループ
for item in apple banana cherry; do
    echo "$item"
done

# 数値範囲(seq)
for i in $(seq 1 5); do
    echo "$i"
done

# C言語スタイル(bash拡張)
for ((i=0; i<5; i++)); do
    echo "$i"
done

# ファイルのループ
for file in *.txt; do
    echo "処理中: $file"
done

# 配列のループ
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

while 文

count=0
while [ $count -lt 5 ]; do
    echo "count = $count"
    ((count++))
done

# ファイルを1行ずつ読む
while IFS= read -r line; do
    echo "$line"
done < input.txt

until 文

while の逆:条件が の間ループします。

count=0
until [ $count -ge 5 ]; do
    echo "count = $count"
    ((count++))
done

break / continue

for i in 1 2 3 4 5; do
    if [ $i -eq 3 ]; then
        continue   # 3をスキップ
    fi
    if [ $i -eq 5 ]; then
        break      # 5で終了
    fi
    echo "$i"
done
# 出力: 1 2 4

7. 関数

# 定義
greet() {
    local name="$1"        # local で関数内ローカル変数
    echo "こんにちは、${name}さん"
}

# 呼び出し
greet "田中"   # こんにちは、田中さん

# 戻り値(終了ステータス 0〜255)
is_even() {
    if (($1 % 2 == 0)); then
        return 0   # 成功(偶数)
    else
        return 1   # 失敗(奇数)
    fi
}

if is_even 4; then
    echo "偶数"
fi

# 文字列を返したい場合はechoを使う
get_date() {
    echo "$(date +%Y-%m-%d)"
}
today=$(get_date)
echo "$today"

local を使わないと変数がグローバルになり、意図しない副作用が出ます。関数内の変数は必ず local を付けましょう。


8. 入出力・リダイレクト

標準入出力

ストリーム fd 説明
標準入力(stdin) 0 キーボード入力
標準出力(stdout) 1 通常の出力
標準エラー出力(stderr) 2 エラーメッセージ

リダイレクト

# 標準出力をファイルへ(上書き)
echo "hello" > output.txt

# 標準出力をファイルへ(追記)
echo "world" >> output.txt

# 標準エラー出力をファイルへ
command 2> error.log

# 標準出力・標準エラーを同じファイルへ
command > all.log 2>&1
command &> all.log   # bash 4+ の短縮形

# 標準エラーを捨てる
command 2>/dev/null

# ファイルから標準入力
command < input.txt

# ヒアドキュメント(複数行の入力)
cat <<EOF
1行目
2行目
3行目
EOF

# ヒアストリング(1行の入力)
grep "pattern" <<< "検索対象の文字列"

パイプ

コマンドの標準出力を次のコマンドの標準入力につなぎます。

# 基本
ls -l | grep ".txt"

# 複数つなげる
cat access.log | grep "ERROR" | sort | uniq -c | sort -rn | head -10

# tee(ファイルにも書きながらパイプ)
command | tee output.txt | grep "ERROR"

9. 終了ステータスとエラーハンドリング

終了ステータス

ls /tmp
echo $?   # 0(成功)

ls /存在しないパス
echo $?   # 0以外(失敗)

set オプション(安全なスクリプト)

#!/bin/bash
set -e   # エラーで即終了
set -u   # 未定義変数の参照でエラー
set -o pipefail  # パイプ途中のエラーも検出

# まとめて書く
set -euo pipefail

trap(終了時の処理)

#!/bin/bash
set -euo pipefail

# スクリプト終了時に必ず実行(クリーンアップ)
trap 'echo "終了処理"; rm -f /tmp/work.$$' EXIT

# エラー時の処理
trap 'echo "エラーが発生しました(行: $LINENO)"' ERR

10. 配列

# 定義
fruits=("apple" "banana" "cherry")

# 参照
echo ${fruits[0]}      # apple
echo ${fruits[1]}      # banana
echo ${fruits[@]}      # apple banana cherry(全要素)
echo ${#fruits[@]}     # 3(要素数)

# 追加
fruits+=("grape")

# 削除
unset fruits[1]        # インデックス1を削除

# スライス
echo ${fruits[@]:1:2}  # インデックス1から2個

連想配列(bash 4.0+)

declare -A user
user["name"]="田中"
user["age"]=30

echo ${user["name"]}   # 田中
echo ${!user[@]}       # キー一覧: name age
echo ${user[@]}        # 値一覧

11. よく使うパターン集

スクリプトのあるディレクトリを取得

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

引数チェック

if [ $# -lt 2 ]; then
    echo "使い方: $0 <入力ファイル> <出力ファイル>"
    exit 1
fi

ファイルが存在しなければ作成

[[ -f "config.txt" ]] || touch "config.txt"

コマンドが存在するか確認

if command -v jq &>/dev/null; then
    echo "jq が使えます"
fi

ログ出力関数

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}

log "処理を開始します"

数値かどうか確認

is_number() {
    [[ "$1" =~ ^[0-9]+$ ]]
}

if is_number "123"; then
    echo "数値です"
fi

演習問題

⭐ 問題1:FizzBuzz

1〜20 を出力し、3の倍数なら「Fizz」、5の倍数なら「Buzz」、両方なら「FizzBuzz」を出力してください。

模範解答
#!/bin/bash
for ((i=1; i<=20; i++)); do
    if ((i % 15 == 0)); then
        echo "FizzBuzz"
    elif ((i % 3 == 0)); then
        echo "Fizz"
    elif ((i % 5 == 0)); then
        echo "Buzz"
    else
        echo "$i"
    fi
done

⭐⭐ 問題2:ファイルバックアップ

引数で指定したファイルを ファイル名.YYYYMMDD.bak という名前でコピーするスクリプトを作成してください。ファイルが存在しない場合はエラーメッセージを出して終了してください。

模範解答
#!/bin/bash
set -euo pipefail

if [ $# -ne 1 ]; then
    echo "使い方: $0 <ファイル名>"
    exit 1
fi

src="$1"

if [ ! -f "$src" ]; then
    echo "エラー: ファイル '$src' が存在しません"
    exit 1
fi

date_str=$(date +%Y%m%d)
dst="${src}.${date_str}.bak"
cp "$src" "$dst"
echo "バックアップ完了: $dst"

⭐⭐⭐ 問題3:CSVの集計

以下のCSVファイルを読み込み、各部署の合計金額を出力してください。

営業部,1000
開発部,2000
営業部,1500
開発部,3000
人事部,500
模範解答
#!/bin/bash
set -euo pipefail

declare -A totals

while IFS=, read -r dept amount; do
    totals["$dept"]=$(( ${totals["$dept"]:-0} + amount ))
done < sales.csv

for dept in "${!totals[@]}"; do
    echo "${dept}: ${totals[$dept]}"
done

実行結果(順序は不定):

営業部: 2500
開発部: 5000
人事部: 500

まとめ

項目 ポイント
変数 = の前後にスペースなし。参照は ${} で括る
条件分岐 文字列比較は =、数値比較は -eq など。[[ ]] を推奨
ループ for in・C言語スタイル・while read を使い分ける
関数 local で変数をローカルに。戻り値は終了ステータスかecho
リダイレクト 2>&1 でstdoutとstderrをまとめる
安全策 set -euo pipefail を先頭に書く習慣を

参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?