シェルスクリプト入門 - 変数/条件分岐/ループ/test【LPIC-1 105.2】

シェルスクリプト入門 - 変数/条件分岐/ループ/test【LPIC-1 105.2】

この記事で達成できること

  • シバン(#!/bin/bash)の役割を説明し、スクリプトを実行可能にできる
  • 変数・位置パラメータ・特殊変数・コマンド置換を正しく使える
  • test / [ ] / [[ ]] で文字列・数値・ファイルを判定できる
  • if / case / for / while / until で制御構造を書ける
  • 終了ステータスと exit&&||、シェル関数を使い分けられる

LPIC-1 主題 105.2「簡単なスクリプトをカスタマイズ・記述する」(LPIC102)の中核。日々の運用を自動化する最小単位の技術。

シェルスクリプトの基本構造とは

シェルスクリプトは「シバン → 変数定義 → 処理本体」で構成する。先頭行のシバンが使用するインタプリタを決め、chmod +x で実行権限を与えると ./script.sh で起動できる。

要素 記法 役割
シバン #!/bin/bash 実行するインタプリタを指定
変数 name=value 値の保持(= の前後にスペース不可)
参照 $name / ${name} 変数の値を展開
コマンド置換 $(command) コマンド出力を値として取得
終了 exit N 終了ステータス N で終了

シバンはスクリプト最初の 2 バイトが #! の場合にカーネルが解釈する。#!/bin/bash なら bash、#!/bin/sh なら POSIX シェルで実行される。man bash の「INVOCATION」が定義する標準的な起動方式。

シバンは必ず1 行目の先頭に置く。前に空行やスペースがあると通常のコメント扱いとなり無効になる。

手順

Step 1: シバンを書いて実行可能にする

cat > hello.sh <<'EOF'
#!/bin/bash
echo "Hello, LPIC"
EOF
chmod +x hello.sh
./hello.sh
Hello, LPIC

1 行目の #!/bin/bash がインタプリタ指定。chmod +x で実行権限を付与し、./hello.sh で起動する。bash hello.sh のように明示的にインタプリタへ渡す場合は実行権限もシバンも不要だが、運用上は両方つけておくのが定石。

Step 2: 変数とクォートを使う

#!/bin/bash
name="Penguin Gym"
count=3
echo "${name} : ${count}"
echo "${name}_log"
Penguin Gym : 3
Penguin Gym_log

代入は name="値" の形で、= の前後にスペースを入れてはならない。参照時は ${name} のように波括弧で囲むと、${name}_log のように直後に文字が続く場合でも変数名の境界が明確になる。値にスペースを含むため "..." で囲んでいる点に注意。

Step 3: 位置パラメータと特殊変数を読む

#!/bin/bash
echo "スクリプト名: $0"
echo "第1引数: $1"
echo "引数の数: $#"
echo "全引数: $@"
スクリプト名: ./args.sh
第1引数: alpha
引数の数: 2
全引数: alpha beta

$0 はスクリプト名、$1$9 は順に引数。$# は引数の個数、$@$* は全引数を表す。"$@" は各引数を個別の文字列として、"$*" は全体を 1 つの文字列として展開する点が異なる(man bash「Special Parameters」)。引数をループ処理するなら "$@" を使う。

変数 意味
$0 スクリプト名
$1$9 位置パラメータ(n 番目の引数)
$# 引数の個数
$@ / $* 全引数
$? 直前のコマンドの終了ステータス
$$ 現在のシェルのプロセス ID
$! 直近にバックグラウンド実行したプロセス ID

Step 4: コマンド置換で出力を変数に取り込む

#!/bin/bash
today=$(date +%F)
files=$(ls /etc | wc -l)
echo "${today} : /etc に ${files} 項目"
2026-05-30 : /etc に 152 項目

$(command) はコマンドの標準出力を文字列として展開する。これがコマンド置換。古い `command`(バッククォート)記法と等価だが、ネストや可読性の点で $(...) が推奨される(man bash「Command Substitution」)。

Step 5: test / [ ] / [[]] で条件を判定する

#!/bin/bash
a="abc"; n=5
[ "$a" = "abc" ] && echo "文字列一致"
[ "$n" -gt 3 ] && echo "数値: 3より大きい"
[ -f /etc/passwd ] && echo "ファイルが存在する"
文字列一致
数値: 3より大きい
ファイルが存在する

[ ... ]test コマンドの別名で、[ の直後と ] の直前には必ずスペースが必要。文字列比較は =!=、数値比較は -eq-ne-lt-gt、ファイルテストは -f(通常ファイル)・-d(ディレクトリ)・-e(存在)・-r-w-x(読み・書き・実行権限)を使う(man test)。bash 拡張の [[ ... ]] は単語分割やパス名展開を抑止し、&&||<> をそのまま書けるため、bash 限定なら扱いやすい。

種類 演算子
文字列 = / != [ "$a" = "$b" ]
数値 -eq -ne -lt -gt -le -ge [ "$n" -eq 0 ]
ファイル -f -d -e -r -w -x [ -d /tmp ]

[ ] の中で数値比較に >< を使うと「リダイレクト」や「文字列の辞書順比較」と解釈され、意図しない結果になる。数値比較は必ず -gt / -lt などを使う。

Step 6: if / case で分岐する

#!/bin/bash
score=$1
if [ "$score" -ge 80 ]; then
    echo "合格"
elif [ "$score" -ge 60 ]; then
    echo "再評価"
else
    echo "不合格"
fi
合格

if 条件; then ... elif 条件; then ... else ... fi が基本形。if は条件コマンドの**終了ステータスが 0(真)**かどうかで分岐する点が重要。値の集合で分けるなら case が読みやすい。

#!/bin/bash
case "$1" in
    start) echo "開始" ;;
    stop)  echo "停止" ;;
    *)     echo "使い方: $0 {start|stop}" ;;
esac
開始

case 値 in パターン) 処理 ;; esac の形。各分岐は ;; で終え、*) が「いずれにも該当しない場合」を受ける。サービス起動スクリプトの定番パターン。

Step 7: for / while / until で繰り返す

#!/bin/bash
for f in *.log; do
    echo "処理中: $f"
done

n=1
while [ "$n" -le 3 ]; do
    echo "回数: $n"
    n=$((n + 1))
done
処理中: access.log
処理中: error.log
回数: 1
回数: 2
回数: 3

for 変数 in 値リスト; do ... done はリストを順に処理する。while 条件; do ... done は条件が真の間繰り返し、until 条件; do ... done は逆に条件が真になるまで繰り返す。$((...)) は算術展開で、整数計算に使う(man bash「Arithmetic Expansion」)。

Step 8: read で入力を受け取り exit で終了する

#!/bin/bash
read -p "名前を入力: " who
if [ -z "$who" ]; then
    echo "名前が空です" >&2
    exit 1
fi
echo "ようこそ ${who}"
exit 0
名前を入力: rina
ようこそ rina

read 変数名 は標準入力を 1 行読み取り変数へ格納する(-p でプロンプト表示)。exit N は終了ステータス N でスクリプトを終了する。慣習として 0 が成功、1 以上が失敗[ -z "$who" ] は文字列が空かどうかの判定。

終了ステータスと && / || はどう使うか

直前のコマンドの終了ステータスは $? に入る。0 が成功、非 0 が失敗。この値を使って &&(前が成功したら次を実行)と ||(前が失敗したら次を実行)でコマンドを連結できる。

mkdir -p /tmp/work && echo "作成成功" || echo "作成失敗"
echo "$?"
作成成功
0

A && B は A の終了ステータスが 0 のときだけ B を実行する。A || B は A が非 0 のときだけ B を実行する。if の判定もこの終了ステータスに基づいており、「真 = 終了ステータス 0」というシェル独自の真偽の定義を理解すると、条件分岐の挙動が一貫して見える。

シェル関数で処理をまとめることもできる。関数内の return N は関数の終了ステータスを、exit N はスクリプト全体を終了させる。

#!/bin/bash
greet() {
    echo "Hello, $1"
    return 0
}
greet "world"
echo "関数の戻り値: $?"
Hello, world
関数の戻り値: 0

関数は 名前() { ... } で定義し、名前 引数 で呼び出す。引数は関数内でも $1$2$# で参照でき、これは位置パラメータが呼び出しごとに切り替わるため。return 省略時は関数内で最後に実行したコマンドの終了ステータスが返る。

よくあるミスと対処

ミス 症状 正しい書き方
代入の = 前後にスペース var: command not found var=value(スペース無し)
クォート不足 スペースを含む値で引数が分割される [ "$var" = "x" ] のように二重引用符で囲む
[ ] のスペース欠落 [: missing ']' [ "$a" = "$b" ][ 直後と ] 直前にスペース)
数値比較に = / > を使用 文字列比較やリダイレクトと誤認 数値は -eq / -gt などを使う
終了ステータスの取り違え if が逆に動く 真は「終了ステータス 0」と理解する

特に多いのが代入時のスペースだ。var = value と書くと、シェルは varコマンド名=value引数と解釈して実行しようとし、command not found になる。

トラブルシューティング

症状: スクリプトが「Permission denied」で起動しない

原因: 実行権限がない、またはシバンが無効

確認:

ls -l script.sh
head -n 1 script.sh

対処: chmod +x script.sh で実行権限を付与する。シバンが 1 行目にあるか、前に空行が無いかも確認する。応急的には bash script.sh で直接実行できる。

症状: 変数代入で「command not found」になる

原因: = の前後にスペースが入っている

確認:

bash -n script.sh

対処: var=value のようにスペースを除く。bash -n は構文チェック(実行せず文法のみ検査)に使える。

症状: 値にスペースが含まれると条件分岐が壊れる

原因: 変数参照をクォートしておらず、単語分割が起きている

確認:

bash -x script.sh

対処: [ "$var" = "値" ] のように変数を二重引用符で囲む。bash -x は各コマンドの展開結果を表示するため、分割の発生箇所を特定できる。

作業完了チェックリスト

  • [ ] 1 行目にシバン(#!/bin/bash)を書いた
  • [ ] chmod +x で実行権限を付与した
  • [ ] 変数代入の = 前後にスペースが無いことを確認した
  • [ ] 変数参照を "$var" でクォートした
  • [ ] [ ] 内のスペースと比較演算子(文字列・数値)を確認した
  • [ ] exit 0 / exit 1 で終了ステータスを返した

まとめ

場面 記法 目的
先頭行 #!/bin/bash インタプリタ指定
値の取得 $(command) コマンド出力を変数化
文字列判定 [ "$a" = "$b" ] 一致 / 不一致
数値判定 [ "$n" -gt 3 ] 大小比較
分岐 ifcase 条件・値による分岐
繰り返し forwhileuntil ループ処理
連結 A && B / A || B 終了ステータスでの連結

シェルスクリプトは Linux 運用自動化の基礎。105.2 を押さえたら、環境変数(105.1)やテキスト処理コマンド・正規表現と組み合わせると、実務的な自動化スクリプトが書けるようになる。

次に読む